From 9997c2822c7c5b81745ebc507eb58000ddf663fe Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 26 Apr 2025 02:45:01 +1000 Subject: [PATCH] translation: backtracking algorithm (#1669) * translation: backtracking algorithm * resolve comments --- .../backtracking_algorithm.md | 110 +++++++++--------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/en/docs/chapter_backtracking/backtracking_algorithm.md b/en/docs/chapter_backtracking/backtracking_algorithm.md index a298ed48c..7f4bfbb5e 100644 --- a/en/docs/chapter_backtracking/backtracking_algorithm.md +++ b/en/docs/chapter_backtracking/backtracking_algorithm.md @@ -1,14 +1,14 @@ # Backtracking algorithms -Backtracking algorithm is a method to solve problems by exhaustive search, where the core idea is to start from an initial state and brute force all possible solutions, recording the correct ones until a solution is found or all possible choices are exhausted without finding a solution. +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 use pre-order traversal to construct a backtracking problem to gradually understand the workings of the backtracking algorithm. +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$, please return a list of nodes. + Given a binary tree, search and record all nodes with a value of $7$ and return them in a list. -For 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 relevant process is shown in the figure below: +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 the figure below: ```src [file]{preorder_traversal_i_compact}-[class]{}-[func]{pre_order} @@ -16,27 +16,27 @@ For this problem, we traverse this tree in pre-order and check if the current no ![Searching nodes in pre-order traversal](backtracking_algorithm.assets/preorder_find_nodes.png) -## Trying and retreating +## Trial and retreat -**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. +**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. -For Example One, visiting each node represents a "try", and passing a leaf node or returning to the parent node's `return` represents "retreat". +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 expand slightly on Example One for clarification. +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 **please return the paths from the root node to these nodes**. + 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 `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: +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: ```src [file]{preorder_traversal_ii_compact}-[class]{}-[func]{pre_order} ``` -In each "try", we record the path by adding the current node to `path`; before "retreating", we need to pop the node from `path` **to restore the state before this attempt**. +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**. -Observe the process shown in the figure below, **we can understand trying and retreating as "advancing" and "undoing"**, two operations that are reverse to each other. +By observing the process shown in the figure below, **the trial is like "advancing", and retreat is like "undoing"**. The later pairs can be seen as a reverse operation to their counterpart. === "<1>" ![Trying and retreating](backtracking_algorithm.assets/preorder_find_paths_step1.png) @@ -71,27 +71,27 @@ Observe the process shown in the figure below, **we can understand trying and re === "<11>" ![preorder_find_paths_step11](backtracking_algorithm.assets/preorder_find_paths_step11.png) -## Pruning +## 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, **requiring that the paths do not contain nodes with a value of $3$**. + 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 returns early, discontinuing further search. The code is as shown: +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: ```src [file]{preorder_traversal_iii_compact}-[class]{}-[func]{pre_order} ``` -"Pruning" is a very vivid noun. As shown in the figure below, in the search process, **we "cut off" the search branches that do not meet the constraints**, avoiding many meaningless attempts, thus enhancing the search efficiency. +"Pruning" is a very vivid noun. As shown in the figure below, 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. ![Pruning based on constraints](backtracking_algorithm.assets/preorder_find_constrained_paths.png) ## Framework code -Next, we attempt to distill the main framework of "trying, retreating, and pruning" from backtracking to enhance the code's universality. +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: @@ -108,9 +108,9 @@ In the following framework code, `state` represents the current state of the pro return # Iterate through all choices for choice in choices: - # Pruning: check if the choice is valid + # Prune: check if the choice is valid if is_valid(state, choice): - # Try: make a choice, update the state + # Trial: make a choice, update the state make_choice(state, choice) backtrack(state, choices, res) # Retreat: undo the choice, revert to the previous state @@ -131,9 +131,9 @@ In the following framework code, `state` represents the current state of the pro } // Iterate through all choices for (Choice choice : choices) { - // Pruning: check if the choice is valid + // Prune: check if the choice is valid if (isValid(state, choice)) { - // Try: make a choice, update the state + // Trial: make a choice, update the state makeChoice(state, choice); backtrack(state, choices, res); // Retreat: undo the choice, revert to the previous state @@ -157,9 +157,9 @@ In the following framework code, `state` represents the current state of the pro } // Iterate through all choices for (Choice choice : choices) { - // Pruning: check if the choice is valid + // Prune: check if the choice is valid if (isValid(state, choice)) { - // Try: make a choice, update the state + // Trial: make a choice, update the state makeChoice(state, choice); backtrack(state, choices, res); // Retreat: undo the choice, revert to the previous state @@ -183,9 +183,9 @@ In the following framework code, `state` represents the current state of the pro } // Iterate through all choices foreach (Choice choice in choices) { - // Pruning: check if the choice is valid + // Prune: check if the choice is valid if (IsValid(state, choice)) { - // Try: make a choice, update the state + // Trial: make a choice, update the state MakeChoice(state, choice); Backtrack(state, choices, res); // Retreat: undo the choice, revert to the previous state @@ -209,9 +209,9 @@ In the following framework code, `state` represents the current state of the pro } // Iterate through all choices for _, choice := range choices { - // Pruning: check if the choice is valid + // Prune: check if the choice is valid if isValid(state, choice) { - // Try: make a choice, update the state + // Trial: make a choice, update the state makeChoice(state, choice) backtrack(state, choices, res) // Retreat: undo the choice, revert to the previous state @@ -235,9 +235,9 @@ In the following framework code, `state` represents the current state of the pro } // Iterate through all choices for choice in choices { - // Pruning: check if the choice is valid + // Prune: check if the choice is valid if isValid(state: state, choice: choice) { - // Try: make a choice, update the state + // Trial: make a choice, update the state makeChoice(state: &state, choice: choice) backtrack(state: &state, choices: choices, res: &res) // Retreat: undo the choice, revert to the previous state @@ -261,9 +261,9 @@ In the following framework code, `state` represents the current state of the pro } // Iterate through all choices for (let choice of choices) { - // Pruning: check if the choice is valid + // Prune: check if the choice is valid if (isValid(state, choice)) { - // Try: make a choice, update the state + // Trial: make a choice, update the state makeChoice(state, choice); backtrack(state, choices, res); // Retreat: undo the choice, revert to the previous state @@ -287,9 +287,9 @@ In the following framework code, `state` represents the current state of the pro } // Iterate through all choices for (let choice of choices) { - // Pruning: check if the choice is valid + // Prune: check if the choice is valid if (isValid(state, choice)) { - // Try: make a choice, update the state + // Trial: make a choice, update the state makeChoice(state, choice); backtrack(state, choices, res); // Retreat: undo the choice, revert to the previous state @@ -313,9 +313,9 @@ In the following framework code, `state` represents the current state of the pro } // Iterate through all choices for (Choice choice in choices) { - // Pruning: check if the choice is valid + // Prune: check if the choice is valid if (isValid(state, choice)) { - // Try: make a choice, update the state + // Trial: make a choice, update the state makeChoice(state, choice); backtrack(state, choices, res); // Retreat: undo the choice, revert to the previous state @@ -339,9 +339,9 @@ In the following framework code, `state` represents the current state of the pro } // Iterate through all choices for choice in choices { - // Pruning: check if the choice is valid + // Prune: check if the choice is valid if is_valid(state, choice) { - // Try: make a choice, update the state + // Trial: make a choice, update the state make_choice(state, choice); backtrack(state, choices, res); // Retreat: undo the choice, revert to the previous state @@ -365,9 +365,9 @@ In the following framework code, `state` represents the current state of the pro } // Iterate through all choices for (int i = 0; i < numChoices; i++) { - // Pruning: check if the choice is valid + // Prune: check if the choice is valid if (isValid(state, &choices[i])) { - // Try: make a choice, update the state + // Trial: make a choice, update the state makeChoice(state, &choices[i]); backtrack(state, choices, numChoices, res, numRes); // Retreat: undo the choice, revert to the previous state @@ -391,9 +391,9 @@ In the following framework code, `state` represents the current state of the pro } // Iterate through all choices for (choice in choices) { - // Pruning: check if the choice is valid + // Prune: check if the choice is valid if (isValid(state, choice)) { - // Try: make a choice, update the state + // Trial: make a choice, update the state makeChoice(state, choice) backtrack(state, choices, res) // Retreat: undo the choice, revert to the previous state @@ -415,17 +415,17 @@ In the following framework code, `state` represents the current state of the pro ``` -Next, we solve Example Three based on the framework code. The `state` is the node traversal path, `choices` are the current node's left and right children, and the result `res` is the list of paths: +Now, we are able to solve Example Three using the framework code. The `state` is the node traversal path, `choices` are the current node's left and right children, and the result `res` is the list of paths: ```src [file]{preorder_traversal_iii_template}-[class]{}-[func]{backtrack} ``` -As per the requirements, after finding a node with a value of $7$, the search should continue, **thus the `return` statement after recording the solution should be removed**. The figure below compares the search processes with and without retaining the `return` statement. +As per the requirements, after finding a node with a value of $7$, the search should continue. **As a result, the `return` statement after recording the solution should be removed**. The figure below compares the search processes with and without retaining the `return` statement. ![Comparison of retaining and removing the return in the search process](backtracking_algorithm.assets/backtrack_remove_return_or_not.png) -Compared to the implementation based on pre-order 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. +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. ## Common terminology @@ -435,12 +435,12 @@ To analyze algorithmic problems more clearly, we summarize the meanings of commo | Term | Definition | Example Three | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| Solution (solution) | A solution is an answer that satisfies specific conditions of the problem, which may have one or more | All paths from the root node to node $7$ that meet the constraint | -| Constraint (constraint) | Constraints are conditions in the problem that limit the feasibility of solutions, often used for pruning | Paths do not contain node $3$ | -| State (state) | State represents the situation of the problem at a certain moment, including choices made | Current visited node path, i.e., `path` node list | -| Attempt (attempt) | An attempt is the process of exploring the solution space based on available choices, including making choices, updating the state, and checking if it's a solution | Recursively visiting left (right) child nodes, adding nodes to `path`, checking if the node's value is $7$ | -| Backtracking (backtracking) | Backtracking refers to the action of undoing previous choices and returning to the previous state when encountering states that do not meet the constraints | When passing leaf nodes, ending node visits, encountering nodes with a value of $3$, terminating the search, and function return | -| Pruning (pruning) | Pruning is a method to avoid meaningless search paths based on the characteristics and constraints of the problem, which can enhance search efficiency | When encountering a node with a value of $3$, no further search is continued | +| Solution | A solution is an answer that satisfies specific conditions of the problem, which may have one or more | All paths from the root node to node $7$ that meet the constraint | +| Constraint | Constraints are conditions in the problem that limit the feasibility of solutions, often used for pruning | Paths do not contain node $3$ | +| State | State represents the situation of the problem at a certain moment, including choices made | Current visited node path, i.e., `path` node list | +| Trial | A trial is the process of exploring the solution space based on available choices, including making choices, updating the state, and checking if it's a solution | Recursively visiting left (right) child nodes, adding nodes to `path`, checking if the node's value is $7$ | +| Retreat | Retreat refers to the action of undoing previous choices and returning to the previous state when encountering states that do not meet the constraints | When passing leaf nodes, ending node visits, encountering nodes with a value of $3$, terminating the search, and the recursion function returns | +| Prune | Prune is a method to avoid meaningless search paths based on the characteristics and constraints of the problem, which can enhance search efficiency | When encountering a node with a value of $3$, no further search is required | !!! tip @@ -450,14 +450,14 @@ To analyze algorithmic problems more clearly, we summarize the meanings of commo The backtracking algorithm is essentially a depth-first search algorithm that attempts all possible solutions until a satisfying solution is found. The advantage of this method is that it can find all possible solutions, and with reasonable pruning operations, it can be highly efficient. -However, when dealing with large-scale or complex problems, **the operational efficiency of backtracking may be difficult to accept**. +However, when dealing with large-scale or complex problems, **the running efficiency of backtracking algorithm may not be acceptable**. -- **Time**: Backtracking algorithms usually need to traverse all possible states in the state space, which can reach exponential or factorial time complexity. -- **Space**: In recursive calls, it is necessary to save the current state (such as paths, auxiliary variables for pruning, etc.). When the depth is very large, the space requirement may become significant. +- **Time complexity**: Backtracking algorithms usually need to traverse all possible states in the state space, which can reach exponential or factorial time complexity. +- **Space complexity**: In recursive calls, it is necessary to save the current state (such as paths, auxiliary variables for pruning, etc.). When the depth is very large, the space need may become significantly bigger. -Even so, **backtracking remains the best solution for certain search problems and constraint satisfaction problems**. For these problems, since it is unpredictable which choices can generate valid solutions, we must traverse all possible choices. In this case, **the key is how to optimize efficiency**, with common efficiency optimization methods being two types. +Even so, **backtracking remains the best solution for certain search problems and constraint satisfaction problems**. For these problems, there is no way to predict which choices can generate valid solutions. We have to traverse all possible choices. In this case, **the key is about how to optimize the efficiency**. There are two common efficiency optimization methods. -- **Pruning**: Avoid searching paths that definitely will not produce a solution, thus saving time and space. +- **Prune**: Avoid searching paths that definitely will not produce a solution, thus saving time and space. - **Heuristic search**: Introduce some strategies or estimates during the search process to prioritize the paths that are most likely to produce valid solutions. ## Typical backtracking problems