--- comments: true --- # 13.1 Backtracking algorithms Backtracking algorithm is a method to solve problems by exhaustive search. Its core concept is to start from an initial state and brutally search for all possible solutions. The algorithm records the correct ones until a solution is found or all possible solutions have been tried but no solution can be found. Backtracking typically employs "depth-first search" to traverse the solution space. In the "Binary tree" chapter, we mentioned that pre-order, in-order, and post-order traversals are all depth-first searches. Next, we are going to use pre-order traversal to solve a backtracking problem. This helps us to understand how the algorithm works gradually. !!! question "Example One" Given a binary tree, search and record all nodes with a value of $7$ and return them in a list. To solve this problem, we traverse this tree in pre-order and check if the current node's value is $7$. If it is, we add the node's value to the result list `res`. The process is shown in Figure 13-1: === "Python" ```python title="preorder_traversal_i_compact.py" def pre_order(root: TreeNode): """Pre-order traversal: Example one""" if root is None: return if root.val == 7: # Record solution res.append(root) pre_order(root.left) pre_order(root.right) ``` === "C++" ```cpp title="preorder_traversal_i_compact.cpp" /* Pre-order traversal: Example one */ void preOrder(TreeNode *root) { if (root == nullptr) { return; } if (root->val == 7) { // Record solution res.push_back(root); } preOrder(root->left); preOrder(root->right); } ``` === "Java" ```java title="preorder_traversal_i_compact.java" /* Pre-order traversal: Example one */ void preOrder(TreeNode root) { if (root == null) { return; } if (root.val == 7) { // Record solution res.add(root); } preOrder(root.left); preOrder(root.right); } ``` === "C#" ```csharp title="preorder_traversal_i_compact.cs" [class]{preorder_traversal_i_compact}-[func]{PreOrder} ``` === "Go" ```go title="preorder_traversal_i_compact.go" [class]{}-[func]{preOrderI} ``` === "Swift" ```swift title="preorder_traversal_i_compact.swift" [class]{}-[func]{preOrder} ``` === "JS" ```javascript title="preorder_traversal_i_compact.js" [class]{}-[func]{preOrder} ``` === "TS" ```typescript title="preorder_traversal_i_compact.ts" [class]{}-[func]{preOrder} ``` === "Dart" ```dart title="preorder_traversal_i_compact.dart" [class]{}-[func]{preOrder} ``` === "Rust" ```rust title="preorder_traversal_i_compact.rs" [class]{}-[func]{pre_order} ``` === "C" ```c title="preorder_traversal_i_compact.c" [class]{}-[func]{preOrder} ``` === "Kotlin" ```kotlin title="preorder_traversal_i_compact.kt" [class]{}-[func]{preOrder} ``` === "Ruby" ```ruby title="preorder_traversal_i_compact.rb" [class]{}-[func]{pre_order} ``` === "Zig" ```zig title="preorder_traversal_i_compact.zig" [class]{}-[func]{preOrder} ``` { class="animation-figure" }
Figure 13-1 Searching nodes in pre-order traversal
## 13.1.1 Trial and retreat **It is called a backtracking algorithm because it uses a "trial" and "retreat" strategy when searching the solution space**. During the search, whenever it encounters a state where it can no longer proceed to obtain a satisfying solution, it undoes the previous choice and reverts to the previous state so that other possible choices can be chosen for the next attempt. In Example One, visiting each node starts a "trial". And passing a leaf node or the `return` statement to going back to the parent node suggests "retreat". It's worth noting that **retreat is not merely about function returns**. We'll expand slightly on Example One question to explain what it means. !!! question "Example Two" In a binary tree, search for all nodes with a value of $7$ and for all matching nodes, **please return the paths from the root node to that node**. Based on the code from Example One, we need to use a list called `path` to record the visited node paths. When a node with a value of $7$ is reached, we copy `path` and add it to the result list `res`. After the traversal, `res` holds all the solutions. The code is as shown: === "Python" ```python title="preorder_traversal_ii_compact.py" def pre_order(root: TreeNode): """Pre-order traversal: Example two""" if root is None: return # Attempt path.append(root) if root.val == 7: # Record solution res.append(list(path)) pre_order(root.left) pre_order(root.right) # Retract path.pop() ``` === "C++" ```cpp title="preorder_traversal_ii_compact.cpp" /* Pre-order traversal: Example two */ void preOrder(TreeNode *root) { if (root == nullptr) { return; } // Attempt path.push_back(root); if (root->val == 7) { // Record solution res.push_back(path); } preOrder(root->left); preOrder(root->right); // Retract path.pop_back(); } ``` === "Java" ```java title="preorder_traversal_ii_compact.java" /* Pre-order traversal: Example two */ void preOrder(TreeNode root) { if (root == null) { return; } // Attempt path.add(root); if (root.val == 7) { // Record solution res.add(new ArrayList<>(path)); } preOrder(root.left); preOrder(root.right); // Retract path.remove(path.size() - 1); } ``` === "C#" ```csharp title="preorder_traversal_ii_compact.cs" [class]{preorder_traversal_ii_compact}-[func]{PreOrder} ``` === "Go" ```go title="preorder_traversal_ii_compact.go" [class]{}-[func]{preOrderII} ``` === "Swift" ```swift title="preorder_traversal_ii_compact.swift" [class]{}-[func]{preOrder} ``` === "JS" ```javascript title="preorder_traversal_ii_compact.js" [class]{}-[func]{preOrder} ``` === "TS" ```typescript title="preorder_traversal_ii_compact.ts" [class]{}-[func]{preOrder} ``` === "Dart" ```dart title="preorder_traversal_ii_compact.dart" [class]{}-[func]{preOrder} ``` === "Rust" ```rust title="preorder_traversal_ii_compact.rs" [class]{}-[func]{pre_order} ``` === "C" ```c title="preorder_traversal_ii_compact.c" [class]{}-[func]{preOrder} ``` === "Kotlin" ```kotlin title="preorder_traversal_ii_compact.kt" [class]{}-[func]{preOrder} ``` === "Ruby" ```ruby title="preorder_traversal_ii_compact.rb" [class]{}-[func]{pre_order} ``` === "Zig" ```zig title="preorder_traversal_ii_compact.zig" [class]{}-[func]{preOrder} ``` In each "trial", we record the path by adding the current node to `path`. Whenever we need to "retreat", we pop the node from `path` **to restore the state prior to this failed attempt**. By observing the process shown in Figure 13-2, **the trial is like "advancing", and retreat is like "undoing"**. The later pairs can be seen as a reverse operation to their counterpart. === "<1>" { class="animation-figure" } === "<2>" { class="animation-figure" } === "<3>" { class="animation-figure" } === "<4>" { class="animation-figure" } === "<5>" { class="animation-figure" } === "<6>" { class="animation-figure" } === "<7>" { class="animation-figure" } === "<8>" { class="animation-figure" } === "<9>" { class="animation-figure" } === "<10>" { class="animation-figure" } === "<11>" { class="animation-figure" }Figure 13-2 Trying and retreating
## 13.1.2 Prune Complex backtracking problems usually involve one or more constraints, **which are often used for "pruning"**. !!! question "Example Three" In a binary tree, search for all nodes with a value of $7$ and return the paths from the root to these nodes, **with restriction that the paths do not contain nodes with a value of $3$**. To meet the above constraints, **we need to add a pruning operation**: during the search process, if a node with a value of $3$ is encountered, it aborts further searching down through the path immediately. The code is as shown: === "Python" ```python title="preorder_traversal_iii_compact.py" def pre_order(root: TreeNode): """Pre-order traversal: Example three""" # Pruning if root is None or root.val == 3: return # Attempt path.append(root) if root.val == 7: # Record solution res.append(list(path)) pre_order(root.left) pre_order(root.right) # Retract path.pop() ``` === "C++" ```cpp title="preorder_traversal_iii_compact.cpp" /* Pre-order traversal: Example three */ void preOrder(TreeNode *root) { // Pruning if (root == nullptr || root->val == 3) { return; } // Attempt path.push_back(root); if (root->val == 7) { // Record solution res.push_back(path); } preOrder(root->left); preOrder(root->right); // Retract path.pop_back(); } ``` === "Java" ```java title="preorder_traversal_iii_compact.java" /* Pre-order traversal: Example three */ void preOrder(TreeNode root) { // Pruning if (root == null || root.val == 3) { return; } // Attempt path.add(root); if (root.val == 7) { // Record solution res.add(new ArrayList<>(path)); } preOrder(root.left); preOrder(root.right); // Retract path.remove(path.size() - 1); } ``` === "C#" ```csharp title="preorder_traversal_iii_compact.cs" [class]{preorder_traversal_iii_compact}-[func]{PreOrder} ``` === "Go" ```go title="preorder_traversal_iii_compact.go" [class]{}-[func]{preOrderIII} ``` === "Swift" ```swift title="preorder_traversal_iii_compact.swift" [class]{}-[func]{preOrder} ``` === "JS" ```javascript title="preorder_traversal_iii_compact.js" [class]{}-[func]{preOrder} ``` === "TS" ```typescript title="preorder_traversal_iii_compact.ts" [class]{}-[func]{preOrder} ``` === "Dart" ```dart title="preorder_traversal_iii_compact.dart" [class]{}-[func]{preOrder} ``` === "Rust" ```rust title="preorder_traversal_iii_compact.rs" [class]{}-[func]{pre_order} ``` === "C" ```c title="preorder_traversal_iii_compact.c" [class]{}-[func]{preOrder} ``` === "Kotlin" ```kotlin title="preorder_traversal_iii_compact.kt" [class]{}-[func]{preOrder} ``` === "Ruby" ```ruby title="preorder_traversal_iii_compact.rb" [class]{}-[func]{pre_order} ``` === "Zig" ```zig title="preorder_traversal_iii_compact.zig" [class]{}-[func]{preOrder} ``` "Pruning" is a very vivid noun. As shown in Figure 13-3, in the search process, **we "cut off" the search branches that do not meet the constraints**. It avoids further unnecessary attempts, thus enhances the search efficiency. { class="animation-figure" }Figure 13-3 Pruning based on constraints
## 13.1.3 Framework code Now, let's try to distill the main framework of "trial, retreat, and prune" from backtracking to enhance the code's universality. In the following framework code, `state` represents the current state of the problem, `choices` represents the choices available under the current state: === "Python" ```python title="" def backtrack(state: State, choices: list[choice], res: list[state]): """Backtracking algorithm framework""" # Check if it's a solution if is_solution(state): # Record the solution record_solution(state, res) # Stop searching return # Iterate through all choices for choice in choices: # Prune: check if the choice is valid if is_valid(state, choice): # Trial: make a choice, update the state make_choice(state, choice) backtrack(state, choices, res) # Retreat: undo the choice, revert to the previous state undo_choice(state, choice) ``` === "C++" ```cpp title="" /* Backtracking algorithm framework */ void backtrack(State *state, vectorFigure 13-4 Comparison of retaining and removing the return in the search process
Compared to the implementation based on pre-order traversal, the code using the backtracking algorithm framework seems verbose. However, it has better universality. In fact, **many backtracking problems can be solved within this framework**. We just need to define `state` and `choices` according to the specific problem and implement the methods in the framework. ## 13.1.4 Common terminology To analyze algorithmic problems more clearly, we summarize the meanings of commonly used terminology in backtracking algorithms and provide corresponding examples from Example Three as shown in Table 13-1.Table 13-1 Common backtracking algorithm terminology