算法——回溯
学习目标:
- 掌握算法入门知识
学习内容:
- 回溯的定义
- 例题详细步骤讲解(求子集、求全排列)
1. 回溯的定义
回溯法是一种通过 试探性搜索 来求解问题的算法思想。一个复杂问题的解决方案是由若干个小的决策步骤组成的决策序列,解决一个问题的所有可能的决策序列构成该问题的解空间。解空间中满足约束条件的决策序列称为可行解。在约束条件下使目标达到最优的可行解称为该问题的最优解。其核心思想是:
(1)逐步构建候选解,并在每一步检查是否满足问题的约束条件。
(2)如果当前路径 不可能得到有效解,则立即 回退(回溯),尝试其他可能的路径。
(3)通过 递归 或 栈(迭代) 实现状态的深入和回退。
回溯法的特点:
(1)系统性:按某种顺序(如 DFS)遍历所有可能的解。
(2)避免无效搜索:通过剪枝(Pruning)提前终止不可能的分支。
适用于组合问题:如排列、组合、子集、棋盘类问题(N 皇后、数独)等。
2. 例题详细步骤讲解
2.1 子集树——求子集
例1:a[]={1,2,3},所有子集是:{},{3},{2},{2,3},{1},{1,3},{1,2},{1,2,3}(输出顺序无关)。
思路:解向量为x[],x[i]=0表示不选择a[i],x[i]=1表示选择a[i]。用 i 扫描数组 a ,也就是说问题的初始状态为(i=0,x的元素均为0),目标状态为(i=n,x为一个解,进行输出)。
- 不选择a[i]元素,x[i]=0 ==> 下一个状态(递归)转向(i+1)。
- 选择a[i]元素,x[i]=1 ==> 下一个状态(递归)转向(i+1)。
代码:
#include <stdio.h>
void dfs(int a[], int n, int i, int x[]) {
if (i >= n) { //拿到一个解向量,进行输出
for (int j = 0; j < n; j++) {
if (x[j] != 0) {
printf("%d ", a[j]); // 打印选中的元素
}
}
printf("\n");
} else {
x[i] = 0; // 不选择a[i]
dfs(a, n, i + 1, x);
x[i] = 1; // 选择a[i]
dfs(a, n, i + 1, x);
}
}
int main() {
int a[] = {1, 2, 3}; // 示例数组
int n = sizeof(a) / sizeof(a[0]); // 计算数组长度
int x[n]; // 辅助数组,用于记录是否选择对应元素
printf("数组的所有子集:\n");
dfs(a, n, 0, x); // 从第0个元素开始回溯
return 0;
}
2.2 排列树——求全排列
例2:有一个含 n 个整数的数组 a ,所有元素均不相同,求其所有元素的全排列。 如:a[]={1,2,3},得到结果是(1,2,3)、(1,3,2)、(2,3,1)、(2,1,3)、(3,1,2)、(3,2,1)。
思路:以1开头的两组后面两元素正好是交换位置的(1,2,3)、(1,3,2),以2、3开头的两组后面两元素正好也是交换位置。说明涉及一个元素交换。再看每两组刚好是1、2、3开头,也可以设置成交换位置得到,如1和1交换,2和1交换,3和1交换。也就是说,整个代码应该围绕交换进行。
DFS(a,n,i) ==> 输出a所有元素即产生一种全排列 i=n-1
DFS(a,n,i) ==> for (j=i;j<n;j++) 其他情况i<n-1
{ 交换a[i]与a[j]; DFS(a,n,i+1); 交换a[i]与a[j]; }
初始状态为(1,2,3),DFS(a,n,0)
(1)i=0,i<n-1,进行for循环,j=i=0,交换a[0]和a[0],得到(1,2,3),递归进入下一层DFS(a,n,1)
(2)i=1,i<n-1,进入for循环,j=i=1,交换a[1]和a[1],得到(1,2,3),递归进入下一层DFS(a,n,2)
(3)i=2,i=n-1,输出一种排列(1,2,3)
(4)回到(2),交换a[1]和a[1],得到(1,2,3)。j++=2,交换a[1]和a[2],得到(1,3,2),递归进入下一层DFS(a,n,2)
(5)i=2,i=n-1,输出一种排列(1,3,2)
(6)回到(2),交换a[1]和a[2],得到(1,2,3)。j++=3,循环结束,回到(1),交换a[0]和a[0],得到(1,2,3)。j++=1,交换a[0]和a[1],得到(2,1,3),递归进入下一层DFS(a,n,1)
(7)i=1,i<n-1,进入for循环,j=i=1,交换a[1]和a[1],得到(2,1,3),递归进入下一层DFS(a,n,2)
(8)i=2,i=n-1,输出一种排列(2,1,3)
(9)回到(7),交换a[1]和a[1],得到(2,1,3)。j++=2,交换a[1]和a[2],得到(2,3,1),递归进入下一层DFS(a,n,2)
(10)i=2,i=n-1,输出一种排列(2,3,1)
(11)回到(7),交换a[1]和a[2],得到(2,1,3)。j++=3,循环结束,回到(1),交换a[0]和a[1],得到(1,2,3)。j++=2,交换a[0]和a[2],得到(3,2,1),递归进入下一层DFS(a,n,1)
(12)第三个大分支省略… 应该会了吧!!!
代码:
#include <stdio.h>
// 交换两个整数的值
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 回溯法生成全排列
void DFS(int a[], int n, int i) {
if (i >= n - 1) { // 递归出口:已处理完所有元素
for (int k = 0; k < n; k++) {
printf("%d ", a[k]); // 打印当前排列
}
printf("\n");
} else {
for (int j = i; j < n; j++) {
swap(&a[i], &a[j]); // 交换a[i]和a[j]
DFS(a, n, i + 1); // 递归处理下一个位置
swap(&a[i], &a[j]); // 恢复交换(回溯)
}
}
}
int main() {
int a[] = {1, 2, 3}; // 示例数组
int n = 3; // 数组长度
printf("数组的全排列:\n");
DFS(a, n, 0); // 从第0个位置开始生成全排列
return 0;
}