Skip to main content

Backtracking

Giving recursion structure a name

It's just a name we give for a certain structure of a recursive method.  it's simply a form of a recursion.

Backtracking is a name for a recursive structure
Agenda

Here is our agenda for this episode, we are going to try in free text and words to describe what backtracking means, how does it relate to other common types of recursions, why you should already know and even practice backtracking without even knowing it, what are the ingredients and structure and steps for creating a backtracking recursive solutions, and briefly describe two problem solutions that involved backtracking.

Backtracking Sudoku


Giving it a name

It's useful to give names to the type's of a recursive solution.  In this way if I tell you my recursion is a tail recursion or my recursion is a backtracking recursion you already have much information about what kind of solution we had in mind for our problem, or you could already imagine how the body of the recursive function looks like so you would be able to immediately actually understand the function.

Building a backtracing recursion

Did you know that when you search a binary tree in a recursive manner you are actually running the backtracking algorithm?

Binary tree search uses backtracking

When we look at merge sort or quick sort I could tell you that I used a recursive algorithm so you would already have hints into how I structured the code in this problem solution, but once I tell you I used a divide and conquer technique you already have many hints into my solution, that is, you know that in each call I divided the input into multiple parts and each such recursive call works on each of these parts as if it's the whole part.

Note that in a recursive divide and conquer merge sort algorithm we do not have a condition that tells the function no no I got this partial solution wrong I didn't manage to sort this half of this array so go back and start again differently, we don't have this, we keep on going and sorting shorter sequences of the whole array, and we just move on and sort them at no point do we say we got this sorting wrong, so we need to dump this solution and get back or in other words backtrack.

In divide and conquer we assume solutions correct

However, if I tell you that I solved a problem with backtracking, then this give you other hints into my recursive function, if I told you I have a backtracking solution, you would know this:

1. The function assumes it can solve fully part of the problem
2. The function has a condition - a test if this sub solution is right or not.
3. If sub solution is correct we move on
4. If sub solution with a condition was wrong we exit and move up the recursion tree.
5. Once we move up the recursion tree, we move on to next try so it's possible that we had a loop of 1..n and inside this for loop we do a recursive call and if this recursive call failed then it moves up.

The cases

Usually what we see is that we have multiple options, and we need to choose one of the options which means we have a condition for each of them.

We can look at a backtracking solution as a recursive tree, when we run this recursion, and we draw that tree all along, then some leaves would be correct solutions and some not, basically we scan the tree, we could look at it as scanning the tree left to right, getting to the most left bottom leaf checking if solution is good or not and continuing until we find a good solution

Look at backtrackign with recursive tree eyes

The general algorithm for backtracking

We get a problem to be solved, we check if we are on a leaf node, if it's the last node, if yes then we check if it's a good solution if true we return true or the solution itself that we saved otherwise we return false which means we backtrack because no good solution on this leaf.

If the current node that we got on the recursive call is not a leaf node then we need now to loop the solutions on this level that we have one by one, and then we call recursively on each such sub problem, and we ask if we have solution for it recursively if yes we return the solution otherwise we return false.

Note the importance of the condition here, as opposed to algorithms of recursive divide and concur such as merge sort where we simply divide the problem and solve each we don't ask if our sorting is correct or not.

There is also an option to do backtracking with non-recursive algorithms, this is pretty standard for recursive algorithms we use a stack.

Non recursive backtracking with a stack

Binary Tree Search

When you search a binary tree you are as we said running the backtracking algorithm when you do this recursively.

You start from the root node and you ask for each node is it the leaf node and if so did we found the target node or not.

We have a condition the condition is simply if we found the value that we wanted or not, if not then we return false, and we backtrack to continue the search.

We simply first check if our node that we accepted is null, if it's null, and we wanted to find a value which is not null then we return false as this value does not exist.

If this node is the value that we wanted we return true because we found the node.

Otherwise, we recursively call on left node to find the value and if found then we return true, and we recursively find on right now and if found we return true.

The n queens

The n queens problem is another canonical problem which can be solved with backtracking which is again just a fancy word of saying that the recursion type or form that we use in order to solve the n queen's problem is of this form:

1. Solve first row we have n options for placing the queen on first row, but we do a for loop for choosing the column for this queen and this would be the very same for loop that we are going to use for the backtracking.
2. Check if our solution is ok so far, if yes continue with calling the recursion on the rest of the problem,
3. if not then simply return from the function it could be that we just finish the function and this would serve as backtracking.


Summary

Backtracking is an important recursive technique where we construct a solution incrementally and in each such step of recursion we check if our current solution is correct or not, if correct we continue, if not we return from the recursive function and move on with our for loop to try the next solution.

Comments

Popular posts from this blog

Functional Programming in Scala for Working Class OOP Java Programmers - Part 1

Introduction Have you ever been to a scala conf and told yourself "I have no idea what this guy talks about?" did you look nervously around and see all people smiling saying "yeah that's obvious " only to get you even more nervous? . If so this post is for you, otherwise just skip it, you already know fp in scala ;) This post is optimistic, although I'm going to say functional programming in scala is not easy, our target is to understand it, so bare with me. Let's face the truth functional programmin in scala is difficult if is difficult if you are just another working class programmer coming mainly from java background. If you came from haskell background then hell it's easy. If you come from heavy math background then hell yes it's easy. But if you are a standard working class java backend engineer with previous OOP design background then hell yeah it's difficult. Scala and Design Patterns An interesting point of view on scala, is

Alternatives to Using UUIDs

  Alternatives to Using UUIDs UUIDs are valuable for several reasons: Global Uniqueness : UUIDs are designed to be globally unique across systems, ensuring that no two identifiers collide unintentionally. This property is crucial for distributed systems, databases, and scenarios where data needs to be uniquely identified regardless of location or time. Standardization : UUIDs adhere to well-defined formats (such as UUIDv4) and are widely supported by various programming languages and platforms. This consistency simplifies interoperability and data exchange. High Collision Resistance : The probability of generating duplicate UUIDs is extremely low due to the combination of timestamp, random bits, and other factors. This collision resistance is essential for avoiding data corruption. However, there are situations where UUIDs may not be the optimal choice: Length and Readability : UUIDs are lengthy (typically 36 characters in their canonical form) and may not be human-readable. In URLs,

Bellman Ford Graph Algorithm

The Shortest path algorithms so you go to google maps and you want to find the shortest path from one city to another.  Two algorithms can help you, they both calculate the shortest distance from a source node into all other nodes, one node can handle negative weights with cycles and another cannot, Dijkstra cannot and bellman ford can. One is Dijkstra if you run the Dijkstra algorithm on this map its input would be a single source node and its output would be the path to all other vertices.  However, there is a caveat if Elon mask comes and with some magic creates a black hole loop which makes one of the edges negative weight then the Dijkstra algorithm would fail to give you the answer. This is where bellman Ford algorithm comes into place, it's like the Dijkstra algorithm only it knows to handle well negative weight in edges. Dijkstra has an issue handling negative weights and cycles Bellman's ford algorithm target is to find the shortest path from a single node in a graph t