i might be missing something about DFS/DLS, wikipedia doesn’t say visited nodes should be concatenated with the current depth...
problem: you have this 2x4 map S=start E=exit
____
E |
|
|
S |
if you are using DFS maxDepth=3 and your move generator is:
dir = {right, left, up, down}
DFS won’t solve this at depth 3 (which means an IDDFS will fail too...)
DFS will try this path first:
____
E |
|
3 2 |
0 1 |
if you are marking the visited positions now E is only reachable at depth 5 since dfs will backtrack to depth 0 and find that the first move ‘up’ is not valid anymore because it was taken at depth 3 already!
the only solution i see is to mark the visited position with the current depth (so you can ignore a ‘visited’ position if visited.depth > currentDepth) which means each position will be visited MANY MANY times in each DFS search when depth=X making it impossible to use in large problems!
in my test if you have enough memory to run a breadth-first search in a large problem it will run a lot faster than a DFS at depth=X even when X is the shortest depth to solve.. it sounds i’m wrong but i just don’t see it why or what i'm doig wrong.. someone please enlighten me up!! (yes that's the question... i don't know what's going on)
this is my search functions to solve puzzles:
BFS (works great but not with very large problems.. uses a lot of RAM)
note i'm not using the hasmap's values (always 0)
HashMap<State, Integer> table = new HashMap();
State bfs() {
State state = this.initState;
Queue<State> pending = new LinkedList();
table.put(state, 0);
pending.add(state);
while ( !pending.isEmpty() ) {
state = pending.remove();
for ( State child : state.getChildren() ) {
if ( !table.containsKey(child) ) {
table.put(child, 0);
if ( child.isSolved() )
return child;
pending.add(child);
}
}
}
return null;
}
DFS (very slow, too much nodes, unusable for large problems)
note that hashMap.put also updates the old values
State dls(State state, int depth) {
if ( depth == maxDepth )
return state.isSolved() ? state : null;
for ( State child : state.getChildren() ) {
if ( !table.containsKey(child) ||
table.get(child) > depth ) {
table.put(child, depth);
State solution = dls(child, depth + 1);
if (solution != null)
return solution;
}
}
return null;
}
State solution(int depth) {
this.maxDepth = depth;
table.put(this.initState, 0);
return dls(this.initState, 0);
}
What problem are you trying to solve? DFS traditionally is used to solve mazes because it there is a theory that it will reach the end of the maze first because it searches depth first, but I believe in general a BFS will be just as fast.
In this case it looks like DFS will work just fine, however, you will visit many nodes in the graph before getting there.
Here is one possible DFS traversal:
____
7 6 |
4 5 |
3 2 |
0 1 |
Where you start at 0 and go to 7. The steps for the DFS are as follows:
Start at 0, add it to a stack.
Stack:{0}
Remove 0, add its three children (3,2,1) to the stack. Mark 0 as visited.
Stack:{3,2,1}
Remove 1, add its three children (0,3,2) to the stack. However, 0 is visited, so skip it. Mark 1 as visited.
Stack:{3,2,3,2}
Remove 2, add its three children (3,4,5) to the stack. Mark 2 as visited.
Stack:{3,2,3,2,5,4,3}
...
Eventually you will end up at E, but your stack will have many nodes in it. Every time you pop a node you'll have to check to see if it is visited, and if it is do nothing.
As you can see, this is a very slow algorithm because the graph is connected.
A better way to find E from S would be to use Shortest Paths or A*
Naive DFS is complete but non-optimal.
That means it guarantees a solution if one exists, but does not guarantee that it is optimal. While one choice for finding an optimal solution is the approach you've taken, it now makes DFS a much worse choice than BFS as it now needs to expand every node up to every possible path, while BFS just needs to expand every node.
For this case, you are correct that BFS is better than DFS, as you have a small search space with many possible paths and are trying to find the optimal solution. Different algorithms are better at different problems. A* and Dijkstra's are examples of algorithms that will perform even better than BFS in this case.
When is it practical to use DFS vs BFS? has some more information about relative benefits of each approach.
I have multiple binary trees stored as an array. In each slot is either nil (or null; pick your language) or a fixed tuple storing two numbers: the indices of the two "children". No node will have only one child -- it's either none or two.
Think of each slot as a binary node that only stores pointers to its children, and no inherent value.
Take this system of binary trees:
0 1
/ \ / \
2 3 4 5
/ \ / \
6 7 8 9
/ \
10 11
The associated array would be:
0 1 2 3 4 5 6 7 8 9 10 11
[ [2,3] , [4,5] , [6,7] , nil , nil , [8,9] , nil , [10,11] , nil , nil , nil , nil ]
I've already written simple functions to find direct parents of nodes (simply by searching from the front until there is a node that contains the child)
Furthermore, let us say that at relevant times, both all trees are anywhere between a few to a few thousand levels deep.
I'd like to find a function
P(m,n)
to find the lowest common ancestor of m and n -- to put more formally, the LCA is defined as the "lowest", or deepest node in which have m and n as descendants (children, or children of children, etc.). If there is none, a nil would be a valid return.
Some examples, given our given tree:
P( 6,11) # => 2
P( 3,10) # => 0
P( 8, 6) # => nil
P( 2,11) # => 2
The main method I've been able to find is one that uses an Euler trace, which turns the given tree (Adding node A as the invisible parent of 0 and 1, with a "value" of -1), into:
A-0-2-6-2-7-10-7-11-7-2-0-3-0-A-1-4-1-5-8-5-9-5-1-A
And from that, simply find the node between your given m and n that has the lowest number; For example, to find P(6,11), look for a 6 and an 11 on the trace. The number between them that is the lowest is 2, and that's your answer. If A (-1) is in between them, return nil.
-- Calculating P(6,11) --
A-0-2-6-2-7-10-7-11-7-2-0-3-0-A-1-4-1-5-8-5-9-5-1-A
^ ^ ^
| | |
m lowest n
Unfortunately, I do believe that finding the Euler trace of a tree that can be several thousands of levels deep is a bit machine-taxing...and because my tree is constantly being changed throughout the course of the programming, every time I wanted to find the LCA, I'd have to re-calculate the Euler trace and hold it in memory every time.
Is there a more memory efficient way, given the framework I'm using? One that maybe iterates upwards? One way I could think of would be the "count" the generation/depth of both nodes, and climb the lowest node until it matched the depth of the highest, and increment both until they find someone similar.
But that'd involve climbing up from level, say, 3025, back to 0, twice, to count the generation, and using a terribly inefficient climbing-up algorithm in the first place, and then re-climbing back up.
Are there any other better ways?
Clarifications
In the way this system is built, every child will have a number greater than their parents.
This does not guarantee that if n is in generation X, there are no nodes in generation (X-1) that are greater than n. For example:
0
/ \
/ \
/ \
1 2 6
/ \ / \ / \
2 3 9 10 7 8
/ \ / \
4 5 11 12
is a valid tree system.
Also, an artifact of the way the trees are built are that the two immediate children of the same parent will always be consecutively numbered.
Are the nodes in order like in your example where the children have a larger id than the parent? If so, you might be able to do something similar to a merge sort to find them.. for your example, the parent tree of 6 and 11 are:
6 -> 2 -> 0
11 -> 7 -> 2 -> 0
So perhaps the algorithm would be:
left = left_start
right = right_start
while left > 0 and right > 0
if left = right
return left
else if left > right
left = parent(left)
else
right = parent(right)
Which would run as:
left right
---- -----
6 11 (right -> 7)
6 7 (right -> 2)
6 2 (left -> 2)
2 2 (return 2)
Is this correct?
Maybe this will help: Dynamic LCA Queries on Trees.
Abstract:
Richard Cole, Ramesh Hariharan
We show how to maintain a data
structure on trees which allows for
the following operations, all in
worst-case constant time. 1. Insertion
of leaves and internal nodes. 2.
Deletion of leaves. 3. Deletion of
internal nodes with only one child. 4.
Determining the Least Common Ancestor
of any two nodes.
Conference: Symposium on Discrete
Algorithms - SODA 1999
I've solved your problem in Haskell. Assuming you know the roots of the forest, the solution takes time linear in the size of the forest and constant additional memory. You can find the full code at http://pastebin.com/ha4gqU0n.
The solution is recursive, and the main idea is that you can call a function on a subtree which returns one of four results:
The subtree contains neither m nor n.
The subtree contains m but not n.
The subtree contains n but not m.
The subtree contains both m and n, and the index of their least common ancestor is k.
A node without children may contain m, n, or neither, and you simply return the appropriate result.
If a node with index k has two children, you combine the results as follows:
join :: Int -> Result -> Result -> Result
join _ (HasBoth k) _ = HasBoth k
join _ _ (HasBoth k) = HasBoth k
join _ HasNeither r = r
join _ r HasNeither = r
join k HasLeft HasRight = HasBoth k
join k HasRight HasLeft = HasBoth k
After computing this result you have to check the index k of the node itself; if k is equal to m or n, you will "extend" the result of the join operation.
My code uses algebraic data types, but I've been careful to assume you need only the following operations:
Get the index of a node
Find out if a node is empty, and if not, find its two children
Since your question is language-agnostic I hope you'll be able to adapt my solution.
There are various performance tweaks you could put in. For example, if you find a root that has exactly one of the two nodes m and n, you can quit right away, because you know there's no common ancestor. Also, if you look at one subtree and it has the common ancestor, you can ignore the other subtree (that one I get for free using lazy evaluation).
Your question was primarily about how to save memory. If a linear-time solution is too slow, you'll probably need an auxiliary data structure. Space-for-time tradeoffs are the bane of our existence.
I think that you can simply loop backwards through the array, always replacing the higher of the two indices by its parent, until they are either equal or no further parent is found:
(defun lowest-common-ancestor (array node-index-1 node-index-2)
(cond ((or (null node-index-1)
(null node-index-2))
nil)
((= node-index-1 node-index-2)
node-index-1)
((< node-index-1 node-index-2)
(lowest-common-ancestor array
node-index-1
(find-parent array node-index-2)))
(t
(lowest-common-ancestor array
(find-parent array node-index-1)
node-index-2))))
As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center for guidance.
Closed 10 years ago.
The community reviewed whether to reopen this question 5 months ago and left it closed:
Not suitable for this site We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
I'm having major trouble understanding recursion at school. Whenever the professor is talking about it, I seem to get it but as soon as I try it on my own it completely blows my brains.
I was trying to solve Towers of Hanoi all night and completely blew my mind. My textbook has only about 30 pages in recursion so it is not too useful. Does anyone know of books or resources that can help clarify this topic?
How do you empty a vase containing five flowers?
Answer: if the vase is not empty, you take out one flower
and then you empty a vase containing four flowers.
How do you empty a vase containing four flowers?
Answer: if the vase is not empty, you take out one flower
and then you empty a vase containing three flowers.
How do you empty a vase containing three flowers?
Answer: if the vase is not empty, you take out one flower
and then you empty a vase containing two flowers.
How do you empty a vase containing two flowers?
Answer: if the vase is not empty, you take out one flower
and then you empty a vase containing one flower.
How do you empty a vase containing one flower?
Answer: if the vase is not empty, you take out one flower
and then you empty a vase containing no flowers.
How do you empty a vase containing no flowers?
Answer: if the vase is not empty, you take out one flower
but the vase is empty so you're done.
That's repetitive. Let's generalize it:
How do you empty a vase containing N flowers?
Answer: if the vase is not empty, you take out one flower
and then you empty a vase containing N-1 flowers.
Hmm, can we see that in code?
void emptyVase( int flowersInVase ) {
if( flowersInVase > 0 ) {
// take one flower and
emptyVase( flowersInVase - 1 ) ;
} else {
// the vase is empty, nothing to do
}
}
Hmm, couldn't we have just done that in a for loop?
Why, yes, recursion can be replaced with iteration, but often recursion is more elegant.
Let's talk about trees. In computer science, a tree is a structure made up of nodes, where each node has some number of children that are also nodes, or null. A binary tree is a tree made of nodes that have exactly two children, typically called "left" and "right"; again the children can be nodes, or null. A root is a node that is not the child of any other node.
Imagine that a node, in addition to its children, has a value, a number, and imagine that we wish to sum all the values in some tree.
To sum value in any one node, we would add the value of node itself to the value of its left child, if any, and the value of its right child, if any. Now recall that the children, if they're not null, are also nodes.
So to sum the left child, we would add the value of child node itself to the value of its left child, if any, and the value of its right child, if any.
So to sum the value of the left child's left child, we would add the value of child node itself to the value of its left child, if any, and the value of its right child, if any.
Perhaps you've anticipated where I'm going with this, and would like to see some code? OK:
struct node {
node* left;
node* right;
int value;
} ;
int sumNode( node* root ) {
// if there is no tree, its sum is zero
if( root == null ) {
return 0 ;
} else { // there is a tree
return root->value + sumNode( root->left ) + sumNode( root->right ) ;
}
}
Notice that instead of explicitly testing the children to see if they're null or nodes, we just make the recursive function return zero for a null node.
So say we have a tree that looks like this (the numbers are values, the slashes point to children, and # means the pointer points to null):
5
/ \
4 3
/\ /\
2 1 # #
/\ /\
## ##
If we call sumNode on the root (the node with value 5), we will return:
return root->value + sumNode( root->left ) + sumNode( root->right ) ;
return 5 + sumNode( node-with-value-4 ) + sumNode( node-with-value-3 ) ;
Let's expand that in place. Everywhere we see sumNode, we'll replace it with the expansion of the return statement:
sumNode( node-with-value-5);
return root->value + sumNode( root->left ) + sumNode( root->right ) ;
return 5 + sumNode( node-with-value-4 ) + sumNode( node-with-value-3 ) ;
return 5 + 4 + sumNode( node-with-value-2 ) + sumNode( node-with-value-1 )
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + sumNode(null ) + sumNode( null )
+ sumNode( node-with-value-1 )
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + 0 + 0
+ sumNode( node-with-value-1 )
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + sumNode(null ) + sumNode( null )
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + 0 + 0
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + 0 + 0
+ 3 + sumNode(null ) + sumNode( null ) ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + 0 + 0
+ 3 + 0 + 0 ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + 0 + 0
+ 3 ;
return 5 + 4
+ 2 + 0 + 0
+ 1
+ 3 ;
return 5 + 4
+ 2
+ 1
+ 3 ;
return 5 + 4
+ 3
+ 3 ;
return 5 + 7
+ 3 ;
return 5 + 10 ;
return 15 ;
Now see how we conquered a structure of arbitrary depth and "branchiness", by considering it as the repeated application of a composite template? each time through our sumNode function, we dealt with only a single node, using a single if/then branch, and two simple return statements that almost wrote themsleves, directly from our specification?
How to sum a node:
If a node is null
its sum is zero
otherwise
its sum is its value
plus the sum of its left child node
plus the sum of its right child node
That's the power of recursion.
The vase example above is an example of tail recursion. All that tail recursion means is that in the recursive function, if we recursed (that is, if we called the function again), that was the last thing we did.
The tree example was not tail recursive, because even though that last thing we did was to recurse the right child, before we did that we recursed the left child.
In fact, the order in which we called the children, and added the current node's value didn't matter at all, because addition is commutative.
Now let's look at an operation where order does matter. We'll use a binary tree of nodes, but this time the value held will be a character, not a number.
Our tree will have a special property, that for any node, its character comes after (in alphabetical order) the character held by its left child and before (in alphabetical order) the character held by its right child.
What we want to do is print the tree in alphabetical order. That's easy to do, given the tree special property. We just print the left child, then the node's character, then right child.
We don't just want to print willy-nilly, so we'll pass our function something to print on. This will be an object with a print( char ) function; we don't need to worry about how it works, just that when print is called, it'll print something, somewhere.
Let's see that in code:
struct node {
node* left;
node* right;
char value;
} ;
// don't worry about this code
class Printer {
private ostream& out;
Printer( ostream& o ) :out(o) {}
void print( char c ) { out << c; }
}
// worry about this code
int printNode( node* root, Printer& printer ) {
// if there is no tree, do nothing
if( root == null ) {
return ;
} else { // there is a tree
printNode( root->left, printer );
printer.print( value );
printNode( root->right, printer );
}
Printer printer( std::cout ) ;
node* root = makeTree() ; // this function returns a tree, somehow
printNode( root, printer );
In addition to the order of operations now mattering, this example illustrates that we can pass things into a recursive function. The only thing we have to do is make sure that on each recursive call, we continue to pass it along. We passed in a node pointer and a printer to the function, and on each recursive call, we passed them "down".
Now if our tree looks like this:
k
/ \
h n
/\ /\
a j # #
/\ /\
## i#
/\
##
What will we print?
From k, we go left to
h, where we go left to
a, where we go left to
null, where we do nothing and so
we return to a, where we print 'a' and then go right to
null, where we do nothing and so
we return to a and are done, so
we return to h, where we print 'h' and then go right to
j, where we go left to
i, where we go left to
null, where we do nothing and so
we return to i, where we print 'i' and then go right to
null, where we do nothing and so
we return to i and are done, so
we return to j, where we print 'j' and then go right to
null, where we do nothing and so
we return to j and are done, so
we return to h and are done, so
we return to k, where we print 'k' and then go right to
n where we go left to
null, where we do nothing and so
we return to n, where we print 'n' and then go right to
null, where we do nothing and so
we return to n and are done, so
we return to k and are done, so we return to the caller
So if we just look at the lines were we printed:
we return to a, where we print 'a' and then go right to
we return to h, where we print 'h' and then go right to
we return to i, where we print 'i' and then go right to
we return to j, where we print 'j' and then go right to
we return to k, where we print 'k' and then go right to
we return to n, where we print 'n' and then go right to
We see we printed "ahijkn", which is indeed in alphabetical order.
We manage to print an entire tree, in alphabetical order, just by knowing how to print a single node in alphabetical order. Which was just (because our tree had the special property of ordering values to the left of alphabetically later values) knowing to print the left child before printing the node's value, and to print the right child after printing the node's value.
And that's the power of recursion: being able to do whole things by knowing only how to do a part of the whole (and knowing when to stop recursing).
Recalling that in most languages, operator || ("or") short-circuits when its first operand is true, the general recursive function is:
void recurse() { doWeStop() || recurse(); }
Luc M comments:
SO should create a badge for this kind of answer. Congratulations!
Thanks, Luc! But, actually, because I edited this answer more than four times (to add the last example, but mostly to correct typos and polish it -- typing on a tiny netbook keyboard is hard), I can't get any more points for it. Which somewhat discourages me from putting as much effort into future answers.
See my comment here on that: https://stackoverflow.com/questions/128434/what-are-community-wiki-posts-in-stackoverflow/718699#718699
Your brain blew up because it got into an infinite recursion. That's a common beginner mistake.
Believe it or not, you already understand recursion, you're just being dragged down by a common, but faulty metaphor for a function: a small box with stuff that comes in and out.
Think instead of a task or procedure, such as "find out more about recursion on the net". That's recursive and you have no problem with it. To complete this task you might:
a) Read a Google's result page for "recursion"
b) Once you've read it, follow the first link on it and...
a.1)Read that new page about recursion
b.1)Once you've read it, follow the first link on it and...
a.2)Read that new page about recursion
b.2)Once you've read it, follow the first link on it and...
As you can see, you've been doing recursive stuff for a long time without any problems.
For how long would you keep doing that task? Forever until your brain blows up? Of course not, you will stop at a given point, whenever you believe you have completed the task.
There's no need to specify this when asking you to "find out more about recursion on the net", because you are a human and you can infer that by yourself.
Computers can't infer jack, so you must include an explicit ending: "find out more about recursion on the net, UNTIL you understand it or you have read a maximum of 10 pages".
You also inferred that you should start at Google's result page for "recursion", and again that's something a computer can't do. The complete description of our recursive task must also include an explicit starting point:
"find out more about recursion on the net, UNTIL you understand it or you have read a maximum of 10 pages and starting at www.google.com/search?q=recursion"
To grok the whole thing, I suggest you try any of these books:
Common Lisp: A Gentle Introduction to Symbolic Computation. This is the cutest non-mathematical explanation of recursion.
The little schemer.
To understand recursion, all you have to do is look on the label of your shampoo bottle:
function repeat()
{
rinse();
lather();
repeat();
}
The problem with this is that there is no termination condition, and the recursion will repeat indefinitely, or until you run out of shampoo or hot water (external termination conditions, similar to blowing your stack).
If you want a book that does a good job of explaining recursion in simple terms, take a look at Gödel, Escher, Bach: An Eternal Golden Braid by Douglas Hofstadter, specifically Chapter 5. In addition to recursion it does a nice job of explaining a number of complex concepts in computer science and math in an understandable way, with one explanation building on another. If you haven't had much exposure to these sorts of concepts before, it can be a pretty mindblowing book.
This is more of a complaint than a question. Do you have a more specific question on recursion? Like multiplication, it's not a thing people write a lot about.
Speaking of multiplication, think of this.
Question:
What's a*b?
Answer:
If b is 1, it's a.
Otherwise, it's a+a*(b-1).
What's a*(b-1)? See the above question for a way to work it out.
Actually you use recursion to reduce the complexity of your problem at hand. You apply recursion until you reach a simple base case that can be solved easily. With this you can solve the last recursive step. And with this all other recursive steps up to your original problem.
I think this very simple method should help you understand recursion. The method will call itself until a certain condition is true and then return:
function writeNumbers( aNumber ){
write(aNumber);
if( aNumber > 0 ){
writeNumbers( aNumber - 1 );
}
else{
return;
}
}
This function will print out all numbers from the first number you'll feed it till 0. Thus:
writeNumbers( 10 );
//This wil write: 10 9 8 7 6 5 4 3 2 1 0
//and then stop because aNumber is no longer larger then 0
What bassicly happens is that writeNumbers(10) will write 10 and then call writeNumbers(9) which will write 9 and then call writeNumber(8) etc. Until writeNumbers(1) writes 1 and then calls writeNumbers(0) which will write 0 butt will not call writeNumbers(-1);
This code is essentially the same as:
for(i=10; i>0; i--){
write(i);
}
Then why use recursion you might ask, if a for-loop does essentially the same. Well you mostly use recursion when you would have to nest for loops but won't know how deep they are nested. For example when printing out items from nested arrays:
var nestedArray = Array('Im a string',
Array('Im a string nested in an array', 'me too!'),
'Im a string again',
Array('More nesting!',
Array('nested even more!')
),
'Im the last string');
function printArrayItems( stringOrArray ){
if(typeof stringOrArray === 'Array'){
for(i=0; i<stringOrArray.length; i++){
printArrayItems( stringOrArray[i] );
}
}
else{
write( stringOrArray );
}
}
printArrayItems( stringOrArray );
//this will write:
//'Im a string' 'Im a string nested in an array' 'me too' 'Im a string again'
//'More nesting' 'Nested even more' 'Im the last string'
This function could take an array which could be nested into a 100 levels, while you writing a for loop would then require you to nest it 100 times:
for(i=0; i<nestedArray.length; i++){
if(typeof nestedArray[i] == 'Array'){
for(a=0; i<nestedArray[i].length; a++){
if(typeof nestedArray[i][a] == 'Array'){
for(b=0; b<nestedArray[i][a].length; b++){
//This would be enough for the nestedAaray we have now, but you would have
//to nest the for loops even more if you would nest the array another level
write( nestedArray[i][a][b] );
}//end for b
}//endif typeod nestedArray[i][a] == 'Array'
else{ write( nestedArray[i][a] ); }
}//end for a
}//endif typeod nestedArray[i] == 'Array'
else{ write( nestedArray[i] ); }
}//end for i
As you can see the recursive method is a lot better.
I'll try to explain it with an example.
You know what n! means? If not: http://en.wikipedia.org/wiki/Factorial
3! = 1 * 2 * 3 = 6
here goes some pseudocode
function factorial(n) {
if (n==0) return 1
else return (n * factorial(n-1))
}
So let's try it:
factorial(3)
is n 0?
no!
so we dig deeper with our recursion:
3 * factorial(3-1)
3-1 = 2
is 2 == 0?
no!
so we go deeper!
3 * 2 * factorial(2-1)
2-1 = 1
is 1 == 0?
no!
so we go deeper!
3 * 2 * 1 * factorial(1-1)
1-1 = 0
is 0 == 0?
yes!
we have a trivial case
so we have
3 * 2 * 1 * 1 = 6
i hope the helped you
Recursion
Method A, calls Method A calls Method A. Eventually one of these method A's won't call and exit, but it's recursion because something calls itself.
Example of recursion where I want to print out every folder name on the hard drive: (in c#)
public void PrintFolderNames(DirectoryInfo directory)
{
Console.WriteLine(directory.Name);
DirectoryInfo[] children = directory.GetDirectories();
foreach(var child in children)
{
PrintFolderNames(child); // See we call ourself here...
}
}
A recursive function is simply a function that calls itself as many times as it needs to do so. It's useful if you need to process something multiple times, but you're unsure how many times will actually be required. In a way, you could think of a recursive function as a type of loop. Like a loop, however, you'll need to specify conditions for the process to be broken otherwise it'll become infinite.
Which book are you using?
The standard textbook on algorithms that is actually good is Cormen & Rivest. My experience is that it teaches recursion quite well.
Recursion is one of the harder parts of programming to grasp, and while it does require instinct, it can be learned. But it does need a good description, good examples, and good illustrations.
Also, 30 pages in general is a lot, 30 pages in a single programming language is confusing. Don't try to learn recursion in C or Java, before you understand recursion in general from a general book.
http://javabat.com is a fun and exciting place to practice recursion. Their examples start fairly light and work through extensive (if you want to take it that far). Note: Their approach is learn by practicing. Here is a recursive function that I wrote to simply replace a for loop.
The for loop:
public printBar(length)
{
String holder = "";
for (int index = 0; i < length; i++)
{
holder += "*"
}
return holder;
}
Here is the recursion to do the same thing. (notice we overload the first method to make sure it is used just like above). We also have another method to maintain our index (similar to the way the for statement does it for you above). The recursive function must maintain their own index.
public String printBar(int Length) // Method, to call the recursive function
{
printBar(length, 0);
}
public String printBar(int length, int index) //Overloaded recursive method
{
// To get a better idea of how this works without a for loop
// you can also replace this if/else with the for loop and
// operationally, it should do the same thing.
if (index >= length)
return "";
else
return "*" + printBar(length, index + 1); // Make recursive call
}
To make a long story short, recursion is a good way to write less code. In the latter printBar notice that we have an if statement. IF our condition has been reached, we will exit the recursion and return to the previous method, which returns to the previous method, etc. If I sent in a printBar(8), I get ********. I am hoping that with an example of a simple function that does the same thing as a for loop that maybe this will help. You can practice this more at Java Bat though.
The truly mathematical way to look at building a recursive function would be as follows:
1: Imagine you have a function that is correct for f(n-1), build f such that f(n) is correct.
2: Build f, such that f(1) is correct.
This is how you can prove that the function is correct, mathematically, and it's called Induction. It is equivalent to have different base cases, or more complicated functions on multiple variables). It is also equivalent to imagine that f(x) is correct for all x
Now for a "simple" example. Build a function that can determine if it is possible to have a coin combination of 5 cents and 7 cents to make x cents. For example, it's possible to have 17 cents by 2x5 + 1x7, but impossible to have 16 cents.
Now imagine you have a function that tells you if it's possible to create x cents, as long as x < n. Call this function can_create_coins_small. It should be fairly simple to imagine how to make the function for n. Now build your function:
bool can_create_coins(int n)
{
if (n >= 7 && can_create_coins_small(n-7))
return true;
else if (n >= 5 && can_create_coins_small(n-5))
return true;
else
return false;
}
The trick here is to realize that the fact that can_create_coins works for n, means that you can substitute can_create_coins for can_create_coins_small, giving:
bool can_create_coins(int n)
{
if (n >= 7 && can_create_coins(n-7))
return true;
else if (n >= 5 && can_create_coins(n-5))
return true;
else
return false;
}
One last thing to do is to have a base case to stop infinite recursion. Note that if you are trying to create 0 cents, then that is possible by having no coins. Adding this condition gives:
bool can_create_coins(int n)
{
if (n == 0)
return true;
else if (n >= 7 && can_create_coins(n-7))
return true;
else if (n >= 5 && can_create_coins(n-5))
return true;
else
return false;
}
It can be proven that this function will always return, using a method called infinite descent, but that isn't necessary here. You can imagine that f(n) only calls lower values of n, and will always eventually reach 0.
To use this information to solve your Tower of Hanoi problem, I think the trick is to assume you have a function to move n-1 tablets from a to b (for any a/b), trying to move n tables from a to b.
Simple recursive example in Common Lisp:
MYMAP applies a function to each element in a list.
1) an empty list has no element, so we return the empty list - () and NIL both are the empty list.
2) apply the function to the first list, call MYMAP for the rest of the list (the recursive call) and combine both results into a new list.
(DEFUN MYMAP (FUNCTION LIST)
(IF (NULL LIST)
()
(CONS (FUNCALL FUNCTION (FIRST LIST))
(MYMAP FUNCTION (REST LIST)))))
Let's watch the traced execution. On ENTERing a function, the arguments are printed. On EXITing a function, the result is printed. For each recursive call, the output will be indented on level.
This example calls the SIN function on each number in a list (1 2 3 4).
Command: (mymap 'sin '(1 2 3 4))
1 Enter MYMAP SIN (1 2 3 4)
| 2 Enter MYMAP SIN (2 3 4)
| 3 Enter MYMAP SIN (3 4)
| | 4 Enter MYMAP SIN (4)
| | 5 Enter MYMAP SIN NIL
| | 5 Exit MYMAP NIL
| | 4 Exit MYMAP (-0.75680256)
| 3 Exit MYMAP (0.14112002 -0.75680256)
| 2 Exit MYMAP (0.9092975 0.14112002 -0.75680256)
1 Exit MYMAP (0.841471 0.9092975 0.14112002 -0.75680256)
This is our result:
(0.841471 0.9092975 0.14112002 -0.75680256)
To explain recursion to a six-year-old, first explain it to a five-year-old, and then wait a year.
Actually, this is a useful counter-example, because your recursive call should be simpler, not harder. It would be even harder to explain recursion to a five-year old, and though you could stop the recursion at 0, you have no simple solution for explaining recursion to a zero-year-old.
To solve a problem using recursion, first sub-divide it into one or more simpler problems that you can solve in the same way, and then when the problem is simple enough to solve without further recursion, you can return back up to higher levels.
In fact, that was a recursive definition of how to solve a problem with recursion.
Children implicitly use recursion, for instance:
Road trip to Disney World
Are we there yet?(no)
Are we there yet?(Soon)
Are we there yet?(Almost...)
Are we there yet?(SHHHH)
Are we there yet?(!!!!!)
At which point the child falls asleep...
This countdown function is a simple example:
function countdown()
{
return (arguments[0] > 0 ?
(
console.log(arguments[0]),countdown(arguments[0] - 1)) :
"done"
);
}
countdown(10);
Hofstadter's Law applied to software projects is also relevant.
The essence of human language is, according to Chomsky, the ability of finite brains to produce what he considers to be infinite grammars. By this he means not only that there is no upper limit on what we can say, but that there is no upper limit on the number of sentences our language has, there's no upper limit on the size of any particular sentence. Chomsky has claimed that the fundamental tool that underlies all of this creativity of human language is recursion: the ability for one phrase to reoccur inside another phrase of the same type. If I say "John's brother's house", I have a noun, "house", which occurs in a noun phrase, "brother's house", and that noun phrase occurs in another noun phrase, "John's brother's house". This makes a lot of sense, and it's an interesting property of human language.
References
Recursion and Human Thought
When working with recursive solutions, I always try to:
Establish the base case first i.e.
when n = 1 in a solution to factorial
Try to come up with a general rule
for every other case
Also there are different types of recursive solutions, there's the divide and conquer approach which is useful for fractals and many others.
It would also help if you could work on simpler problems first just to get the hang of it. Some examples are solving for the factorial and generating the nth fibonacci number.
For references, I highly recommend Algorithms by Robert Sedgewick.
Hope that helps. Good luck.
A recursive function is like a spring you compress a bit on each call. On each step, you put a bit of information (current context) on a stack. When the final step is reached, the spring is released, collecting all values (contexts) at once!
Not sure this metaphor is effective... :-)
Anyway, beyond the classical examples (factorial which is the worst example since it is inefficient and easily flattened, Fibonacci, Hanoi...) which are a bit artificial (I rarely, if ever, use them in real programming cases), it is interesting to see where it is really used.
A very common case is to walk a tree (or a graph, but trees are more common, in general).
For example, a folder hierarchy: to list the files, you iterate on them. If you find a sub-directory, the function listing the files call itself with the new folder as argument. When coming back from listing this new folder (and its sub-folders!), it resumes its context, to the next file (or folder).
Another concrete case is when drawing a hierarchy of GUI components: it is common to have containers, like panes, to hold components which can be panes too, or compound components, etc. The painting routine calls recursively the paint function of each component, which calls the paint function of all the components it holds, etc.
Not sure if I am very clear, but I like to show real world use of teaching material, as it was something I was stumbling upon in the past.
Ouch. I tried to figure out the Towers of Hanoi last year. The tricky thing about TOH is it's not a simple example of recursion - you have nested recursions which also change the roles of towers on each call. The only way I could get it to make sense was to literally visualize the movement of the rings in my mind's eye, and verbalize what the recursive call would be. I would start with a single ring, then two, then three. I actually ordered the game on the internet. It took me maybe two or three days of cracking my brains to get it.
Think a worker bee. It tries to make honey. It does its job and expects other worker bees to make rest of the honey. And when the honeycomb is full, it stops.
Think it as magic. You have a function that has the same name with the one you are trying to implement and when you give it the subproblem, it solves it for you and the only thing you need to do is to integrate the solution of your part with the solution it gave you.
For example, we want to calculate the length of a list. Lets call our function magical_length and our magical helper with magical_length
We know that if we give the sublist which does not have the first element, it will give us the length of the sublist by magic. Then only thing we need to think is how to integrate this information with our job. The length of the first element is 1 and magic_counter gives us the length of sublist n-1, therefore total length is (n-1) + 1 -> n
int magical_length( list )
sublist = rest_of_the_list( list )
sublist_length = magical_length( sublist ) // you can think this function as magical and given to you
return 1 + sublist_length
However this answer is incomplete because we didn't consider what happens if we give an empty list. We thought that the list we have always has at least one element. Therefore we need to think about what should be the answer if we are given an empty list and answer is obviously 0. So add this information to our function and this is called base/edge condition.
int magical_length( list )
if ( list is empty) then
return 0
else
sublist_length = magical_length( sublist ) // you can think this function as magical and given to you
return 1 + sublist_length