tags:

views:

854

answers:

10

If we consider recursive function in C/C++, are they useful in any way? Where exactly they are used mostly? Are there any advantages in terms of memory by using recursive functions?

Edit: is the recursion better or using a while loop?

+9  A: 

Recursive functions are primarily used for ease of designing algorithms. For example you need to traverse a directory tree recursively - its depth it limited, so you're quite likely to never face anything like too deep recursion and consequent stack overflow, but writing a tree traversal recursively is soo much easier, then doing the same in iterative manner.

In most cases recursive functions don't save memory compared to iterative solutions. Even worse they consume stack memory which is relatively scarse.

sharptooth
-1: Please define "relatively scarse". On todays computers, stack is relatively unlimited -- only the virtual memory sets the boundaries. Stack has the advantage, that it can not become fragmented like memory that is allocated on the heap -- fragmentation is one big threat for memory management, since you can run out of memory also without really using all memory.
Juergen
For the advantage of stack-memory, see also my answer.
Juergen
Stack memory is scarse as set amount of address space is allocated to it. Modern memory management can easily handle a growing stack but when the address space is full there is nowhere left to grow and the stack segfaults. This problem gets worse when threads get involved as their stack address space is usually even more limited than main. This may no longer be an issue in 64bit address spaces as the stack(s) can be assigned insanely large sections of the address space without limiting the instruction or heap sections.
caspin
@Caspin: What you write is correct. But you argue that when the stack overflows (no memory left), than stack segfaults. But you should also consider the case that you have a recursive problem (not tail-recursive, of course) then you need to allocate memory on the heap to avoid recursion. But your heap is just as well limited as the stack. When your heap is finished of, you are also in trouble -- only few programs can handle an heap overflow. So the argument "stack is scarse" must be also considered for the heap.
Juergen
@Juergen: When the stack is extended, that must be done by allocating more memory directly after where the stack ended, which isn't always possible. Heap allocations can be made anywhere. The stack can't always be extended, even if there is plenty of heap memory still free.
jalf
@jalf: can you explain (and add reference) to "The stack can't always be extended, even if there is plenty of heap memory still free." My information is, that your claim is only in pathological cases true (you have overextended your heap). When you make such claims, than add proof!!
Juergen
I am not talking about being out of memory. I'm talking about being out of address space. Imagine an address space of 10. 1 and 2 store the instructions. 3 holds globals and statics. The heap starts at 4 and can grow 7. The stack starts at 10 and can grow to 8 (stacks grow down). When the stack tries to grow down into the heap (address 7), it will fail with a segfault, meaning the segment was not available because that segment has been assigned to the heap. Generally the address space reserve for the heap is much larger than the stack. In Linux the stack only reserves 2MB by default.
caspin
In early operating systems, programs did not delineate where the stack ended. Instead of a meaningful segfault, the stack would overwrite values in the heap and vis versa. The situation gets much more complex when threads get involved. We need space to store thread local variables and the thread's stack. I imagine they could just be stored in the heap but that is not how it's done on Linux. Thread local variables are a difficult problem when the thread's start address could be anywhere in the heap, instead a thread table is preallocated, which is why the # of threads is limited.
caspin
@Caspin: You always speak about pathological cases: Threads, huge heaps, very old operation systems or pre-32Bit architectures. I am speaking not about those.
Juergen
@Juergen: In Ubuntu 9.10 the default stack size is 8MB. Same for Mac OSX. The stack size can be increased but the size is still fixed. A stack cannot grow beyond its initial size. Ignore threads, ignore the broken way it used to be done, ignore 16 bit real mode processors.
caspin
@Caspin: In one comment you wrote 2MB, now it is 8MB -- what is it?
Juergen
+4  A: 

they have many uses and some things become very difficult to impossible without them. iterating trees for instance.

ThanosPapathanasiou
+1  A: 

When using recursion, you can store data on the stack (effectively, in the calling contexts of all the functions above the current instance) that you would have instead to store in the heap with dynamic allocation if you were trying to do the same thing with a while loop. Think of most divide-and-conquer algorithms where there are two things to do on each call (that is, one of the calls is not tail-recursive).

And with respect to Tom's interesting comment/subquestion, this advantage of recursive functions is maybe particularly noticeable in C where the management of dynamic memory is so basic. But that doesn't make it very specific to C.

Pascal Cuoq
You can store data on the stack in a `while` loop too. Just declare an array of items and use that as the stack.
Paul Stephenson
@Paul That's implementing one's own stack. It forces the programmer to compute the maximum stack depth of his algorithm himself so that he can allocate an array of the right size.
Pascal Cuoq
Yes, it is implementing one's own stack (data structure) using the program stack (for local variables). The programmer should be aware of the maximum stack depth anyway since the stack for recursive function calls is not infinite either. I don't think the dynamic memory allocation you describe is any easier than implementing one's own stack. Recursion is easier than both of these, which is one reason to prefer it in some situations.
Paul Stephenson
A: 

There are two reasons I see for using recursion:

  • an algorithm operates on recursive data structures (like e.g. a tree)
  • an algorithm is of recursive nature (often happens for mathematical problems as recursion often offers beautiful solutions)

Handle recursion with care as there is always the danger of infinite recursion.

Thorsten79
As there also is the danger of infinite loops ... Of course, infinite recursions are more feared, since the situations can be more complicated and a stack overflow is following (while some infinite loops are not overflowing anything -- as long they don't allocate more and more memory), but infinite loops must also be avoided like infinite recursion.
Juergen
Infinite loops are much more easily to understand than infinite recursion. Complex recursion with multiple functions involved is very difficult to debug properly.
Thorsten79
+1  A: 

Dynamic programming is a key area where recursion is crucial, though it goes beyond that (remembering sub-answers can give drastic performance improvements). Algorithms are where recursion is normally used, rather than typical day to day coding. It's more a computer-science concept than a programming one.

John
A: 

Recursive functions make it easier to code solutions that have a recurrence relation.

For instance the factorial function has a recurrence relation:

 factorial(0) = 1
 factorial(n) = n * factorial(n-1)

Below I have implemented factorial using recursion and looping.

The recursive version and recurrence relation defined above look similar and is hence easier to read.

Recursive:

double factorial ( int n )
{
 return ( n ? n * factorial ( n-1 ) : 1 );
}

Looping:

double factorial ( int n )
{
 double result = 1;
 while ( n > 1 )
 {
  result = result * n;
  n--;
 }
 return result;
}

One more thing:

The recursive version of factorial includes a tail call to itself and can be tail-call optimized. This brings the space complexity of recursive factorial down to the space complexity of the iterative factorial.

ardsrk
"The recursive version of factorial includes a tail call to itself". No, it doesn't. The last operation is not the recursive call, but the multiplication of n with the result of the recursive call. The tail-recursive version of factorial is usually defined using a helper function which takes an accumulator as its second argument.
sepp2k
You deliberately make your recursive version shorter, using a shortened if/else statement. Expanding that and improving your loop version yield very similar functions. This isn't a good example of recursion being _better_.
John
+2  A: 

Implement QuickSort with and without using recursion, then you can tell by yourself if it's useful or not.

fortran
+4  A: 

Recursion definitively has advantages at problems with a recursive nature. Other posters named some of them.

To use the capability of C for recursion definitively has advantages in memory management. When you try to avoid recursion, most of the time an own stack or other dynamic data type is used to break the problem. This involves dynamic memory management in C/C++. Dynamic memory management is costly and errorprone!

You can't beat the stack

On the other hand, when you just use the stack and use recursion with local variables -- the memory management is just simple and the stack is most of the time more time-efficient then all the memory management you can do by yourself or with plain C/C++ memory-management. The reason is that the system stack is such a simple and convenient data structure with low overhead and it is implemented using special processor operations that are optimized for this work. Believe me, you can't beat that, since compilers, operation systems and processors are optimized for stack manipulations for decades!

PS: Also the stack does not become fragmented, like heap memory does easily. This way it is also possible to save memory by using the stack / recursion.

Juergen
-1. Assuming that the stack can just grow indefinitely is just a recipe for segfaults. The stack has some definite advantages, yes, but relying on it to store large amounts of data is just a bad idea.
jalf
As in your other comment: Add some proof please! The stack is (according to my information) in the same way limited as the heap. So, when you advocating to use the heap, you just get into different trouble (since the heap is also limited!!) Again: DON'T GIVE OTHERS BAD MARKS WITHOUT PROOF!
Juergen
@Juergen: I absolutely agree with you. Given the choice, choose the stack. However, jalf is correct. When storing large amounts of data, or using an algorithm with very deep recursion, the stack can quickly run out of space. On Linux, by default, the stack can only grow to a maximum of 2MB. Windows has a similar (smaller) limit. Both allow users to specify a larger stack size but neither have an option for unbounded stack growth.
caspin
@Caspin: I never argued to "store large amounts of data" on the stack. This is just a black-hearted misinterpretion of jalf. I argued, that the stack has definitive advantages -- speed and no fragmentation. For most recursive algorithms, 8MB (like in your other comment) or even 2MB are plenty of space. I had never a case, where the stack overflowed. Even when you have 2k of local variables (that is real plenty!) in one call, you can handle upto 1000 recursion-depths -- very seldom you will have such cases in practice!
Juergen
+1  A: 

I often find recursive algorithms easier to understand because they involve less mutable state. Consider the algorithm for determining the greatest common divisor of two numbers.

unsigned greatest_common_divisor_iter(unsigned x, unsigned y)
{
    while (y != 0)
    {
        unsigned temp = x % y;
        x = y;
        y = temp;
    }
    return x;
}

unsigned greatest_common_divisor(unsigned x, unsigned y)
{
    return y == 0 ? x : greatest_common_divisor(y, x % y);
}

There is too much renaming going on in the iterative version for my taste. In the recursive version, everything is immutable, so you could even make x and y const if you liked.

FredOverflow
A: 

One thing that is worth mentioning is that in most functional languages (Scheme for example), you can take advantage of tail call optimizations, and thus you can use recursive functions without increasing the amount of memory in your stack.

Basically, complex recursive tail calls can runs flawlessly in Scheme while in C/C++ the same ones will create a stack overflow.

dudico