Figure 13-1 Searching nodes in preorder traversal
## 13.1.1 Trying and retreating **The reason it is called backtracking is that the algorithm uses a "try" and "retreat" strategy when searching the solution space**. When the algorithm encounters a state where it can no longer progress or fails to achieve a satisfying solution, it undoes the previous choice, reverts to the previous state, and tries other possible choices. For Example One, visiting each node represents a "try", and passing a leaf node or returning to the parent node's `return` represents "retreat". It's worth noting that **retreat is not merely about function returns**. We expand slightly on Example One for clarification. !!! question "Example Two" In a binary tree, search for all nodes with a value of $7$ and **please return the paths from the root node to these nodes**. Based on the code from Example One, we need to use a list `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): """前序遍历:例题二""" if root is None: return # 尝试 path.append(root) if root.val == 7: # 记录解 res.append(list(path)) pre_order(root.left) pre_order(root.right) # 回退 path.pop() ``` === "C++" ```cpp title="preorder_traversal_ii_compact.cpp" /* 前序遍历:例题二 */ void preOrder(TreeNode *root) { if (root == nullptr) { return; } // 尝试 path.push_back(root); if (root->val == 7) { // 记录解 res.push_back(path); } preOrder(root->left); preOrder(root->right); // 回退 path.pop_back(); } ``` === "Java" ```java title="preorder_traversal_ii_compact.java" /* 前序遍历:例题二 */ void preOrder(TreeNode root) { if (root == null) { return; } // 尝试 path.add(root); if (root.val == 7) { // 记录解 res.add(new ArrayList<>(path)); } preOrder(root.left); preOrder(root.right); // 回退 path.remove(path.size() - 1); } ``` === "C#" ```csharp title="preorder_traversal_ii_compact.cs" /* 前序遍历:例题二 */ void PreOrder(TreeNode? root) { if (root == null) { return; } // 尝试 path.Add(root); if (root.val == 7) { // 记录解 res.Add(new ListFigure 13-2 Trying and retreating
## 13.1.2 Pruning 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, **requiring 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 returns early, discontinuing further search. The code is as shown: === "Python" ```python title="preorder_traversal_iii_compact.py" def pre_order(root: TreeNode): """前序遍历:例题三""" # 剪枝 if root is None or root.val == 3: return # 尝试 path.append(root) if root.val == 7: # 记录解 res.append(list(path)) pre_order(root.left) pre_order(root.right) # 回退 path.pop() ``` === "C++" ```cpp title="preorder_traversal_iii_compact.cpp" /* 前序遍历:例题三 */ void preOrder(TreeNode *root) { // 剪枝 if (root == nullptr || root->val == 3) { return; } // 尝试 path.push_back(root); if (root->val == 7) { // 记录解 res.push_back(path); } preOrder(root->left); preOrder(root->right); // 回退 path.pop_back(); } ``` === "Java" ```java title="preorder_traversal_iii_compact.java" /* 前序遍历:例题三 */ void preOrder(TreeNode root) { // 剪枝 if (root == null || root.val == 3) { return; } // 尝试 path.add(root); if (root.val == 7) { // 记录解 res.add(new ArrayList<>(path)); } preOrder(root.left); preOrder(root.right); // 回退 path.remove(path.size() - 1); } ``` === "C#" ```csharp title="preorder_traversal_iii_compact.cs" /* 前序遍历:例题三 */ void PreOrder(TreeNode? root) { // 剪枝 if (root == null || root.val == 3) { return; } // 尝试 path.Add(root); if (root.val == 7) { // 记录解 res.Add(new ListFigure 13-3 Pruning based on constraints
## 13.1.3 Framework code Next, we attempt to distill the main framework of "trying, retreating, and pruning" 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: # Pruning: check if the choice is valid if is_valid(state, choice): # Try: 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 preorder traversal, the code implementation based on the backtracking algorithm framework seems verbose, but 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