Reputation: 693
I am trying to broaden my limited understanding of recursion. I have been working on a Binary Search Tree and am currently attempting to implement a traversal function. I started off with using a Pre-order Traversal, did that no problem and moved on to In-order which seemed much trickier to me. I couldn't figure out a recursive solution on my own so I googled it and found many variations of the same answer-
function inOrderHelper(root) {
if (root !== null) {
inOrderHelper(root.left);
console.log(root.data);
inOrderHelper(root.right);
}
}
Very simple code with even simpler explanations, none of which helped me undestand what exactly this function is doing. So, since you guys have been so helpful previously, I was hoping you could help me expand my knowledge here.
Upvotes: 0
Views: 97
Reputation: 25895
The easiest way to understand code is usually to try it out with a debugger. Chrome has an excellent debugger which you can use to step through the code, line by line, as it runs in real time.
The next easiest way is to use console logs to print out what's happening, which is how most programmers over a certain age would figure out what was happening before debuggers made it easier.
Since I can't sit next to you with a debugger open, let's do the next best thing and add some console logs so we can see what's happening:
function inOrderHelper(root) {
console.group("Entering inOrderHelper with ", root);
if (root !== null && root !== undefined) {
console.log("Root is not null, so continue");
console.group("Traversing down the left node");
inOrderHelper(root.left);
console.groupEnd();
console.log("The root value is ", root.value);
console.group("Traversing down the right node");
inOrderHelper(root.right);
console.groupEnd();
} else {
console.log("Root is null, so back up");
}
console.log("Exiting inOrderHelper");
console.groupEnd();
}
So let's try an example BST:
Which could be constructed to look something like this in JavaScript:
{
left: {
left: {
value: 1,
},
value: 2,
right: {
value: 3,
},
},
value: 4,
right: {
value: 5,
},
}
You can run this code in your browser's dev tools by pasting in the above function (and hitting enter) and then calling it like so:
inOrderHelper({
left: {
left: {
value: 1,
},
value: 2,
right: {
value: 3,
},
},
value: 4,
right: {
value: 5,
},
})
The result should look something like this:
Entering inOrderHelper with {left: {…}, value: 4, right: {…} }
Root is not null, so continue
Traversing down the left node
Entering inOrderHelper with {left: {…}, value: 2, right: {…}}
Root is not null, so continue
Traversing down the left node
Entering inOrderHelper with { value: 1 }
Root is not null, so continue
Traversing down the left node
Entering inOrderHelper with undefined
Root is null, so back up
Exiting inOrderHelper
The root value is 1
Traversing down the right node
Entering inOrderHelper with undefined
Root is null, so back up
Exiting inOrderHelper
Exiting inOrderHelper
The root value is 2
Traversing down the right node
Entering inOrderHelper with { value: 3 }
Root is not null, so continue
Traversing down the left node
Entering inOrderHelper with undefined
Root is null, so back up
Exiting inOrderHelper
The root value is 3
Traversing down the right node
Entering inOrderHelper with undefined
Root is null, so back up
Exiting inOrderHelper
Exiting inOrderHelper
Exiting inOrderHelper
The root value is 4
Traversing down the right node
Entering inOrderHelper with { value: 5 }
Root is not null, so continue
Traversing down the left node
Entering inOrderHelper with undefined
Root is null, so back up
Exiting inOrderHelper
The root value is 5
Traversing down the right node
Entering inOrderHelper with undefined
Root is null, so back up
Exiting inOrderHelper
Exiting inOrderHelper
Exiting inOrderHelper
You can also use online tools, like BinaryTreeVisualizer, to see this demonstrated with animations.
How does the program know to stop before the tree is finished? It seems to me that it should continue to go to the runner's left node until it is null, at which point it will skip the console.log
Notice that when the function recurses down the left side, when the recursive function returns, control returns to the parent function, which continues on down the right side. When a recursive function returns, it doesn't immediately end the parent function. The parent function treats the recursive function like any other function. It calls it and then, when it returns, goes on to the next thing.
How does the program know that a node has already been printed? It seems to me that it would just print the minimum value repeatedly or once before traversing to the maximum value but apparently the nodes are being checked off somehow.
This is where javascript gets a little bit confusing. Essentially, the function is trying to go down the left and right side, but if the root value is a string, like "B"
, then root.left
and root.right
refer to properties that don't exist. In javascript, rather than throwing an error, it just returns undefined
. So when we recurse on root.left
and that value is undefined
then we do nothing.
So, in our example tree:
{
left: {
left: {
value: 1,
},
value: 2,
right: {
value: 3,
},
},
value: 4,
right: {
value: 5,
},
}
Our first root is { left: { ... }, value: 4, right: { value: 5 } }
When we go to the left, root is now { left: { value: 1 }, value: 2, right: { value: 3 } }
.
When we go left again, the root is now { value: 1 }
.
When we go left again, the root is now undefined
, so we do nothing and return to the previous call where the root is { value: 1 }
.
We print 1
.
Then we go to the right and the root is now undefined
, so we do nothing and return to the previous call where the root is { value: 1 }
.
We're done with { value: 1 }
, so we return to the previous call where the root is { left: { value: 1 }, value: 2, right: { value: 3 } }
We print 2
.
Now we go down to the right, and the process repeats as it did for the left, printing 3
.
We then go back up to the previous root, { left: { ... }, value: 4, right: { value: 5 } }
We print 4
.
The we go to the right and, as with the previous examples, print 5
.
We return and, since we've arrived at the original function call, we return and end the program.
The end result is that we printed 1
, 2
, 3
, 4
, 5
, in that order.
How are all the values printed? For example, if the second smallest value is the right node of the third smallest, value, how is the second smallest value account for?
I'm not sure what you're asking, but it's important to note that this function does not sort the tree. It just reports the values. So if the BST was not constructed properly (e.g. a smaller value is the right of a larger value), then it will print those values out of order as well.
Upvotes: 2
Reputation: 83587
As other's have stated, using the Chrome debugger to step through this code is a great way to start to understand what is going on. I suggest also drawing your own pictures similar to what other's have shown in their answers to help you follow along with what the debugger is doing.
How does the program know to stop before the tree is finished? It seems to me that it should continue to go to the runner's left node until it is null, at which point it will skip the console.log
The function continually visits the left node until it reaches a leaf. Then it gradually returns, prints out the data, and visits each right node. The program will terminate after it has visited every node in the tree.
How does the program know that a node has already been printed? It seems to me that it would just print the minimum value repeatedly or once before traversing to the maximum value but apparently the nodes are being checked off somehow.
It doesn't keep track of visited nodes as you assume. It traverses as far as it can down the left side of the tree before working its way back to the root and visiting the right side along the way.
How are all the values printed? For example, if the second smallest value is the right node of the third smallest, value, how is the second smallest value account for?
Because at each recursive call, it prints the value of the current "root".
Upvotes: 0
Reputation: 55779
1 function inOrder(root) {
2 if (!root) return;
3 inOrderHelper(root.left);
4 console.log(root.data);
5 inOrderHelper(root.right);
6 }
7
8 inOrder(root) // 2 4 6 7 9
Q1
Line 2 stops the recursion progressing forever.
At the bottom left of the graph, node 2 is evaluated, then inOrder
is invoked with left
as an argument, which is undefined
. Line 2 evaluates to true
and immediately returns. Once it has returned to the calling point, execution continues. Line 4 is evaluated, meaning 2
is printed to the console. Then line 5 is evaluated. This happens every time the algorithm hits a node without a left or a right subtree.
Q2
It uses the stack-based nature of the programming language itself, to keep track of where it is in the graph. Every time the function is called a new stack frame is added to the stack, and every time a function completes, that stack frame is removed.
Q3
Nodes are printed according to their position in the graph, not their value.
const root = {
data: 7,
left: {
data: 4,
left: {
data: 2
},
right: {
data: 6
}
},
right: {
data: 9
}
}
function inOrder(root) {
if (!root)
return;
inOrder(root.left);
console.log(root.data);
inOrder(root.right);
}
inOrder(root)
Upvotes: 2