Lec 10 - More Recursion
Slides:
Tower of Hanoi
The code to print out the solution to the tower of hanoi
where print
is a helper function to display the move to standard output.
In the solve_tower_of_hanoi()
, Line 6 basically does moving k-1 disks from source
to the placeholder
, so when calling the function, the dest
is our placeholder
. Then Line 7 is the actual move, where we move the remaining k-th disk from source
to dest
. Line 8 does moving the k-1 disks from the placeholder
to the dest
, so when calling the source
is our placeholder
.
Running Time
The recurrence relation in Tower of Hanoi problem is: , it time complexity is
Tips
Treat the bottom disk as non-existing, so every time you move the bottom disk to the
dest
, you can just ignore them and continue on the rest k-1 disks.Every time you use the recursion function, make sure you are solving the same subproblem.
Make sure your original problem can be solved by subproblem.
Regarding the point 2, there is an interesting question to think about. Wait for prof uploading the slides.
Permutations
Suppose the problem we are facing now is to find all the permutations of a string (each character is unique), e.g. abcd
.
The base case is trivial, it is when we have one character left (same as we have reached the end of the string), just print this character.
The recursive case, however, is not that trivial. Use wishful thinking, suppose that given a string of length k
, we already know all the permutations of its substring with length k-1
(excluding itself). How can we find all the permutations of the string with length k
? Since we have k
options for the leading character, so we need to iterate through these k
options and find all their corresponding k-1
permutations. And here comes the awesome idea of swap()
.
where swap(char a[], size_t i, size_t j)
just swaps the i
-th character with the j
-th character in string a
.
Why is swap()
so awesome? Wait for lecture slides to be uploaded
Running Time
The recurrence relation here is . And its time complexity is
Note that in Permutations, , since we need to print out the string of length , it is . Amazing right!
Tips
The Permutation Problem gives us a method on how to do recursion in a tree-like graph below (traverse to the deepest node then return ), this idea will be pretty important in the following N-Queens problem below also.
N-Queens
First, let's clarify how we gonna represent the "board" of our N-Queens. Instead of using a 2-D array, we use the unique column id to represent the position of each queen. Below is an example,
The advantage of using this representation is that:
we won't care about the row and column constraint anymore in solving, we only need to care about the diagonal constraint;
The problem becomes very similar to the Permutations problem we have discussed above.
Before the discussion, let's write some helper function to help us check the diganol constraint
Find all solutions
Use the idea of permutations, we can find all the possible permutations (first) and check whether they are valid or not (after).
We will use the helper function threaten_each_other_diagonally(char queens[], size_t last_row)
Use wishful thinking to understand, in this problem, we need to find all the solutions, that is we will stop after we brute-force all the possible cases, so there is no restriction for our function type and it should be void.
The highlighted code (Line 13) does the diagonal constraint check after we have reached the last step - after we have put the last queen on our board. However, this is actually very time consuming and will do lots of useless work.
Find one solution
To stop after we have found the first solution, we just slightly change our code to below
Use wishful thinking to understand, what this function does is to find one solution/judge whether there is a solution or not, so obviously the type of the function should be boolean.
Line 22 is to modify/try our current solution, and immediately after that, Line 23 is to check whether there is a solution to our smaller problem or not. If there is, then we rerturn TRUE (the if
condition), which means we have found a solution to our problem. And immediately after that, we return TRUE (inside the if
branch) from our current function call, which means we have found a solution! (Use the call-stack diagram to understand that)
All the return statements indicate whether we have found a solution or not (including both trivial cases and the recursive/smaller problem cases).
However, this is still a bit time consuming. (Wait for lecture slides to come up)
Find one solution faster
To optimize it, we should reduce the amount of useless work. That is when we move on to the next queen, we check whether it violates the diagonal constraint with the previous queen or not. If it does, we don't need to go deeper, we just move to next position.
This is equivalent to adding another if
structure outside our call recursion call.
Given that solving the nqueens problems takes , then . See 5. Time Complexity for Nqueens for the explanation!
Last updated