Exploring Decision Trees
Permutation problems can be solved by using decision trees. The depth of the decision tree represents the number of possible outcomes. The breadth of the decision tree represents the ordering by which decisions are made. In programming, we can represent decision trees by using the recursive backtracking algorithm.
Recursive Backtracking Algorithm
function recurse(depth) { //some basecase here for (var i = 0;i < breadth.length;i++) { applyValue(breadth[i]); recurse(breadth[i], depth++); removeValue(breadth[i]); } }
This code represents generalization of the recursive backtracking
algorithm. This algorithm can be used to model decision trees. The depth of the
tree can be represented by the depth of the recursive stack. The breadth of the
tree can be represented by the number of iterations performed by the for
loop
within each recursive stackframe. The actual decision making process can be
represented by the code within the for
loop.
Solving Permutation Problems using the Recursive Backtracking Algorithm
Lets explore using the recursive backtracking algorithm to solve a permutation problem. This is an algorithm I created for finding all possible anagrams of any given word:
function anagrams(originalString) { var resultStorage = {}; (function recurse(buildUpString, originalString) { if (originalString === '') { resultStorage[buildUpString] = 1; return; } for (var i = 0;i < originalString.length;i++) { recurse(buildUpString + originalString[i], originalString.substring(0, i) + originalString.substring(i + 1)); } })('', originalString); return Object.keys(resultStorage); }; var anagrams = anagrams('abc'); console.log(anagrams); // [ 'abc', 'acb', 'bac', 'bca', 'cab', 'cba' ]
This solution follows the structure of the recursive backtracking algorithm
shown previously. As you can see it has a for
loop
within a recursive function. Within the for
loop there is code which controls how the final result is generated.
The code within the for
loop appears as though it
does not resemble the 3 lines of code from within the for
loop of
the recursive backtracking algorithm, but in actuality this one
line of code is performing the behavior of all three of the lines of code from
within the recursive backtracking algorithm's for
loop. The one line of code within the
for
loop of the anagrams
algorithms perform the
behavior of each one implicitly.
The anagrams
solution contains 3 variables which are essential to its
structure as a decision tree. Algorithm's which represent decision tree often
have some form of these variables.
resultStorage
buildUpString
originalString
resultStorage
is the variable which accumulates all the
values of the buildUpString
variable that have passed the success
base case. if we are using a pure recursive
solution then this variable will not exist. In such a case, each branch of the
decision tree would just return it's own results to the previous branch.
All results would accumulate together as each stackframe returned.
The buildUpVariable
initially starts off empty and accumulates more
values as the depth of the decision tree increases. When the final level of
decision tree is hit, which can be represented in code as hitting a base case, we add the
value of the buildUpString
to resultStorage
, then
traverse back up the decision tree by returning from stack frames.
The originalString
variable represents the initial value passed into the decision
tree which is the basis for what all possible permutations can be.
Bringing it all together
On every recursive call, the
buildUpString
gets bigger, and the originalString
gets
smaller. When originalString
has length 0, we add the
value of buildUpString
to resultStorage
.
Returning from recursive stack frames removes values from the
buildUpString
in the order they were added to it. In essence, returning takes the values away from
the buildUpString
and puts them back into
originalString
. This process represents our traversal
back up the decision tree.