
回溯算法(Backtracking)是一种通过探索所有可能的候选解来找出所有解的算法。当探索到某一步时,如果发现这个选择不能得到有效解,就回退一步重新选择。本文将详细介绍回溯算法的核心思想,并通过 N 皇后问题和全排列问题来深入理解。

回溯算法是一种试探性的搜索算法,它通过逐步构建解,当发现当前路径不可能得到解时,就回退到上一步,尝试其他可能。
核心思想:
回溯是递归的一种应用,但更强调:
fn backtrack(当前状态, 路径, 结果集) {
// 1. 终止条件
if 满足终止条件 {
结果集.push(路径.clone());
return;
}
// 2. 遍历所有可能的选择
for 选择 in 所有可能的选择 {
// 3. 做选择(修改状态)
路径.push(选择);
修改状态;
// 4. 递归(进入下一层)
backtrack(新状态, 路径, 结果集);
// 5. 撤销选择(恢复状态)
路径.pop();
恢复状态;
}
}/// 回溯算法通用模板(Rust 风格)
fn backtrack_template<T: Clone>(
state: &mut Vec<T>,
choices: &[T],
result: &mut Vec<Vec<T>>,
is_valid: fn(&[T]) -> bool,
) {
// 终止条件:找到完整解
if state.len() == choices.len() {
if is_valid(state) {
result.push(state.clone());
}
return;
}
// 遍历所有可能的选择
for choice in choices {
// 剪枝:如果当前选择不合法,跳过
if !is_valid_choice(state, choice) {
continue;
}
// 做选择
state.push(choice.clone());
// 递归
backtrack_template(state, choices, result, is_valid);
// 撤销选择(回溯)
state.pop();
}
}全排列问题: 给定一个不含重复数字的数组,返回其所有可能的全排列。
示例:
[1, 2, 3][[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]/// 全排列问题 - 基础回溯实现
fn permute(nums: Vec<i32>) -> Vec<Vec<i32>> {
let mut result = Vec::new();
let mut path = Vec::new();
let mut used = vec![false; nums.len()];
fn backtrack(
nums: &[i32],
path: &mut Vec<i32>,
used: &mut Vec<bool>,
result: &mut Vec<Vec<i32>>,
) {
// 终止条件:路径长度等于数组长度
if path.len() == nums.len() {
result.push(path.clone());
return;
}
// 遍历所有可能的选择
for i in 0..nums.len() {
// 剪枝:如果数字已使用,跳过
if used[i] {
continue;
}
// 做选择
path.push(nums[i]);
used[i] = true;
// 递归
backtrack(nums, path, used, result);
// 撤销选择(回溯)
path.pop();
used[i] = false;
}
}
backtrack(&nums, &mut path, &mut used, &mut result);
result
}
fn main() {
let nums = vec![1, 2, 3];
let result = permute(nums);
println!("全排列结果:");
for perm in &result {
println!("{:?}", perm);
}
println!("共 {} 种排列", result.len());
}
输出:
全排列结果:
[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]
共 6 种排列/// 全排列 - 使用交换实现(更节省空间)
fn permute_swap(nums: &mut Vec<i32>) -> Vec<Vec<i32>> {
let mut result = Vec::new();
fn backtrack(nums: &mut Vec<i32>, start: usize, result: &mut Vec<Vec<i32>>) {
// 终止条件
if start == nums.len() {
result.push(nums.clone());
return;
}
// 从 start 开始,依次与后面的元素交换
for i in start..nums.len() {
// 做选择:交换
nums.swap(start, i);
// 递归
backtrack(nums, start + 1, result);
// 撤销选择:交换回来
nums.swap(start, i);
}
}
backtrack(nums, 0, &mut result);
result
}
fn main() {
let mut nums = vec![1, 2, 3];
let result = permute_swap(&mut nums);
println!("使用交换实现的全排列:");
for perm in &result {
println!("{:?}", perm);
}
}/// 全排列 - 处理重复元素
fn permute_unique(nums: Vec<i32>) -> Vec<Vec<i32>> {
let mut nums = nums;
nums.sort(); // 排序以便去重
let mut result = Vec::new();
let mut path = Vec::new();
let mut used = vec![false; nums.len()];
fn backtrack(
nums: &[i32],
path: &mut Vec<i32>,
used: &mut Vec<bool>,
result: &mut Vec<Vec<i32>>,
) {
if path.len() == nums.len() {
result.push(path.clone());
return;
}
for i in 0..nums.len() {
// 剪枝1:已使用
if used[i] {
continue;
}
// 剪枝2:重复元素,只使用第一个未使用的
// 如果当前元素与前一个相同,且前一个未使用,跳过
if i > 0 && nums[i] == nums[i - 1] && !used[i - 1] {
continue;
}
path.push(nums[i]);
used[i] = true;
backtrack(nums, path, used, result);
path.pop();
used[i] = false;
}
}
backtrack(&nums, &mut path, &mut used, &mut result);
result
}
fn main() {
let nums = vec![1, 1, 2];
let result = permute_unique(nums);
println!("含重复元素的全排列:");
for perm in &result {
println!("{:?}", perm);
}
// 输出:[[1, 1, 2], [1, 2, 1], [2, 1, 1]]
}N 皇后问题: 在 n×n 的棋盘上放置 n 个皇后,使得它们不能相互攻击(即任意两个皇后不能在同一行、同一列或同一对角线上)。
示例:
/// N 皇后问题 - 基础回溯实现
fn solve_n_queens(n: usize) -> Vec<Vec<String>> {
let mut result = Vec::new();
let mut board = vec![vec!['.'; n]; n];
fn is_valid(board: &[Vec<char>], row: usize, col: usize) -> bool {
let n = board.len();
// 检查列
for i in 0..row {
if board[i][col] == 'Q' {
return false;
}
}
// 检查左上对角线
let mut i = row as i32 - 1;
let mut j = col as i32 - 1;
while i >= 0 && j >= 0 {
if board[i as usize][j as usize] == 'Q' {
return false;
}
i -= 1;
j -= 1;
}
// 检查右上对角线
let mut i = row as i32 - 1;
let mut j = col as i32 + 1;
while i >= 0 && j < n as i32 {
if board[i as usize][j as usize] == 'Q' {
return false;
}
i -= 1;
j += 1;
}
true
}
fn backtrack(
board: &mut Vec<Vec<char>>,
row: usize,
result: &mut Vec<Vec<String>>,
) {
let n = board.len();
// 终止条件:所有行都放置了皇后
if row == n {
let solution: Vec<String> = board
.iter()
.map(|row| row.iter().collect())
.collect();
result.push(solution);
return;
}
// 尝试在当前行的每一列放置皇后
for col in 0..n {
// 剪枝:检查是否可以放置
if !is_valid(board, row, col) {
continue;
}
// 做选择
board[row][col] = 'Q';
// 递归:处理下一行
backtrack(board, row + 1, result);
// 撤销选择
board[row][col] = '.';
}
}
backtrack(&mut board, 0, &mut result);
result
}
fn main() {
let n = 4;
let solutions = solve_n_queens(n);
println!("{} 皇后问题的解(共 {} 种):", n, solutions.len());
for (i, solution) in solutions.iter().enumerate() {
println!("\n解 {}:", i + 1);
for row in solution {
println!("{}", row);
}
}
}输出:
4 皇后问题的解(共 2 种):
解 1:
.Q..
...Q
Q...
..Q.
解 2:
..Q.
Q...
...Q
.Q../// N 皇后问题 - 优化实现(使用集合记录冲突)
fn solve_n_queens_optimized(n: usize) -> Vec<Vec<String>> {
let mut result = Vec::new();
let mut queens = Vec::new(); // 记录每行皇后的列位置
let mut cols = std::collections::HashSet::new();
let mut diag1 = std::collections::HashSet::new(); // 主对角线
let mut diag2 = std::collections::HashSet::new(); // 副对角线
fn backtrack(
row: usize,
n: usize,
queens: &mut Vec<usize>,
cols: &mut std::collections::HashSet<usize>,
diag1: &mut std::collections::HashSet<i32>,
diag2: &mut std::collections::HashSet<i32>,
result: &mut Vec<Vec<String>>,
) {
if row == n {
// 构建解
let solution: Vec<String> = (0..n)
.map(|r| {
(0..n)
.map(|c| if queens[r] == c { 'Q' } else { '.' })
.collect()
})
.collect();
result.push(solution);
return;
}
for col in 0..n {
let d1 = row as i32 - col as i32;
let d2 = row as i32 + col as i32;
// 剪枝:检查冲突
if cols.contains(&col) || diag1.contains(&d1) || diag2.contains(&d2) {
continue;
}
// 做选择
queens.push(col);
cols.insert(col);
diag1.insert(d1);
diag2.insert(d2);
// 递归
backtrack(row + 1, n, queens, cols, diag1, diag2, result);
// 撤销选择
queens.pop();
cols.remove(&col);
diag1.remove(&d1);
diag2.remove(&d2);
}
}
backtrack(0, n, &mut queens, &mut cols, &mut diag1, &mut diag2, &mut result);
result
}
fn main() {
let n = 8;
let solutions = solve_n_queens_optimized(n);
println!("{} 皇后问题共有 {} 种解", n, solutions.len());
}/// 可视化 N 皇后解
fn print_queens_solution(solution: &[String]) {
let n = solution.len();
// 打印顶部边框
println!("┌{}┐", "─".repeat(n * 2 + 1));
for (i, row) in solution.iter().enumerate() {
print!("│ ");
for (j, ch) in row.chars().enumerate() {
if ch == 'Q' {
print!("♛ ");
} else {
if (i + j) % 2 == 0 {
print!(" ");
} else {
print!("█ ");
}
}
}
println!("│");
}
// 打印底部边框
println!("└{}┘", "─".repeat(n * 2 + 1));
}
fn main() {
let solutions = solve_n_queens(4);
println!("4 皇后问题的解:\n");
for (i, solution) in solutions.iter().enumerate() {
println!("解 {}:", i + 1);
print_queens_solution(solution);
println!();
}
}剪枝是回溯算法最重要的优化手段:
/// 剪枝优化示例:数独求解
fn is_valid_sudoku(board: &[Vec<char>], row: usize, col: usize, num: char) -> bool {
let n = 9;
// 检查行
for j in 0..n {
if board[row][j] == num {
return false;
}
}
// 检查列
for i in 0..n {
if board[i][col] == num {
return false;
}
}
// 检查 3x3 宫格
let start_row = (row / 3) * 3;
let start_col = (col / 3) * 3;
for i in start_row..start_row + 3 {
for j in start_col..start_col + 3 {
if board[i][j] == num {
return false;
}
}
}
true
}对于有重叠子问题的情况,可以使用记忆化:
use std::collections::HashMap;
/// 记忆化回溯:计算路径数量
fn unique_paths_with_memo(
m: i32,
n: i32,
memo: &mut HashMap<(i32, i32), i32>,
) -> i32 {
if m == 1 || n == 1 {
return 1;
}
if let Some(&result) = memo.get(&(m, n)) {
return result;
}
let result = unique_paths_with_memo(m - 1, n, memo)
+ unique_paths_with_memo(m, n - 1, memo);
memo.insert((m, n), result);
result
}对于深度不确定的问题,可以使用迭代加深:
/// 迭代加深搜索
fn iddfs(start: State, target: State, max_depth: usize) -> Option<Vec<State>> {
for depth in 0..=max_depth {
let mut visited = std::collections::HashSet::new();
if let Some(path) = dfs_limited(start.clone(), target.clone(), depth, &mut visited) {
return Some(path);
}
}
None
}/// 子集问题:返回所有可能的子集
fn subsets(nums: Vec<i32>) -> Vec<Vec<i32>> {
let mut result = Vec::new();
let mut path = Vec::new();
fn backtrack(
nums: &[i32],
start: usize,
path: &mut Vec<i32>,
result: &mut Vec<Vec<i32>>,
) {
// 每个节点都是解
result.push(path.clone());
for i in start..nums.len() {
path.push(nums[i]);
backtrack(nums, i + 1, path, result);
path.pop();
}
}
backtrack(&nums, 0, &mut path, &mut result);
result
}
fn main() {
let nums = vec![1, 2, 3];
let result = subsets(nums);
println!("子集:");
for subset in &result {
println!("{:?}", subset);
}
// 输出:[], [1], [1,2], [1,2,3], [1,3], [2], [2,3], [3]
}/// 组合问题:从 n 个数中选 k 个
fn combine(n: i32, k: i32) -> Vec<Vec<i32>> {
let mut result = Vec::new();
let mut path = Vec::new();
fn backtrack(
start: i32,
n: i32,
k: i32,
path: &mut Vec<i32>,
result: &mut Vec<Vec<i32>>,
) {
// 终止条件
if path.len() == k as usize {
result.push(path.clone());
return;
}
// 剪枝:剩余元素不足以构成 k 个
if (n - start + 1) < (k - path.len() as i32) {
return;
}
for i in start..=n {
path.push(i);
backtrack(i + 1, n, k, path, result);
path.pop();
}
}
backtrack(1, n, k, &mut path, &mut result);
result
}/// 单词搜索:在二维网格中搜索单词
fn exist(board: Vec<Vec<char>>, word: String) -> bool {
let word: Vec<char> = word.chars().collect();
let mut visited = vec![vec![false; board[0].len()]; board.len()];
fn backtrack(
board: &[Vec<char>],
word: &[char],
visited: &mut Vec<Vec<bool>>,
row: usize,
col: usize,
index: usize,
) -> bool {
// 终止条件:找到完整单词
if index == word.len() {
return true;
}
// 边界检查
if row >= board.len() || col >= board[0].len() {
return false;
}
// 已访问或不匹配
if visited[row][col] || board[row][col] != word[index] {
return false;
}
// 做选择
visited[row][col] = true;
// 四个方向搜索
let directions = [(0, 1), (0, -1), (1, 0), (-1, 0)];
for (dr, dc) in &directions {
let new_row = row as i32 + dr;
let new_col = col as i32 + dc;
if new_row >= 0 && new_col >= 0 {
if backtrack(
board,
word,
visited,
new_row as usize,
new_col as usize,
index + 1,
) {
return true;
}
}
}
// 撤销选择
visited[row][col] = false;
false
}
// 从每个位置开始搜索
for i in 0..board.len() {
for j in 0..board[0].len() {
if backtrack(&board, &word, &mut visited, i, j, 0) {
return true;
}
}
}
false
}全排列问题:
N 皇后问题:
子集问题:
use std::time::Instant;
fn benchmark_backtrack() {
println!("回溯算法性能测试:\n");
// 测试全排列
println!("全排列(n = 8):");
let start = Instant::now();
let result = permute((1..=8).collect());
let duration = start.elapsed();
println!(" 结果数: {}", result.len());
println!(" 耗时: {:?}\n", duration);
// 测试 N 皇后
println!("N 皇后(n = 8):");
let start = Instant::now();
let result = solve_n_queens_optimized(8);
let duration = start.elapsed();
println!(" 结果数: {}", result.len());
println!(" 耗时: {:?}\n", duration);
}实现一个数独求解器,使用回溯算法填充 9×9 的数独网格。
生成 n 对有效括号的所有组合。例如 n=3,输出:["((()))", "(()())", "(())()", "()(())", "()()()"]
给定一个仅包含数字 2-9 的字符串,返回所有可能的字母组合(手机键盘映射)。
给定一个字符串,将字符串分割成一些子串,使每个子串都是回文串。返回所有可能的分割方案。
给定一个只包含数字的字符串,返回所有可能的有效 IP 地址。
给定一个无重复元素的数组和一个目标数,找出所有可以使数字和为目标数的组合。
通过本章学习,你应该掌握:
✅ 回溯算法的核心思想 ✅ 回溯算法的通用模板 ✅ 全排列问题的多种实现 ✅ N 皇后问题的解法 ✅ 剪枝优化技巧 ✅ 复杂度分析方法
关键要点:
回溯算法适用场景: