回溯法
在问题的解空间树中,按深度优先策略,根节点出发搜索解空间树,算法搜索至解空间的任意节点时,先判断该节点是否包含问题的解,若肯定不包含,则跳过对以该节点为根的子树的搜索,逐层向其祖先结点回溯。否则进入该子树,继续按根深度优先搜索。
子集树和排列树的区别
书上写的
- 当所给问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间树称为子集树。子集树的时间复杂度为2^n
- 当所给的问题时确定n个元素满足某种性质的排列时,相应的解空间树成为排列树。排列树的时间复杂度为n!
我自己的概括
- 子集树的思想为深度优先算法,从根节点出发,不断访问符合条件的子节点,(如果不符合,则进行剪枝并访问该结点的兄弟节点),当访问到叶节点的时候,进行输出或者计数等操作,然后对叶节点的兄弟结点进行访问,直到访问结束,再访问叶节点的根节点的兄弟结点(回溯)。
- 排列树也是从根节点触发,不断访问符合条件的子节点,但是排列树的t值和子集树的t值不一样,子集树的t值可以代表每一个作业,而排列树的t值仅仅可以代表作业的顺序。
// n后问题
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
#define M 100
int sum = 0;
int ok(int k,int *x) {
for (int j= 1; j < k; j++) {
if ((abs(k - j) == abs(x[j] - x[k]))||(x[j]==x[k])) {
return 0;
}
}
return 1;
}
void BackTrack(int t,int *x,int n) {
if (t > n) {
sum++;
for (int i = 1; i <= n; i++) {
printf("%d", x[i]);
}
printf("\n");
}
else
for (int i = 1; i <= n; i++) {
x[t] = i;
if (ok(t,x)) BackTrack(t + 1,x,n);
}
}
int main()
{
int n;
scanf("%d", &n);
int x[M];
for (int i = 0; i <= n; i++){
x[i] = 0;
}
BackTrack(1, x, n);
printf("%d", sum);
}
排列树
排列树是我利用了排列树的思想,在子集树的基础上修改得来的
把排列树改成子集树,有几点需要注意:
- 子集树求得是集合,所以初始化集合的时候,把他们都赋值为0,表示都没有放东西,然后在回溯的时候,会将他们一一赋值。而排列树求的是排列,所以我们要把需要被排列的值作为初值赋给数组。
- 子集树在从根节点到子节点的过程中,每个结点的取值都有n中可能性,我们为了不重复,需要通过合法性判断这个子集有没有重复,而排列树不需要这么做,排列树选完一个值之后,就会从剩下的值中继续挑选,这样我们就不需要判断有没有重复了。
// n后问题
//
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
#define M 100
int sum = 0;
int ok(int k,int *x) {
for (int j= 1; j < k; j++) {
if ((abs(k - j) == abs(x[j] - x[k])) {
return 0;
}
}
return 1;
}
void swap(int* x, int t, int i) {
int q = x[t];
x[t] = x[i];
x[i] = q;
}
void BackTrack(int t,int *x,int n) {
if (t > n) {
sum++;
for (int i = 1; i <= n; i++) {
printf("%d", x[i]);
}
printf("\n");
}
else
for (int i = t; i <= n; i++) {
swap(x,t, i);
if (ok(t,x)) BackTrack(t + 1,x,n);
swap(x,t, i);
}
}
int main()
{
int n;
scanf("%d", &n);
int x[M];
for (int i = 0; i <= n; i++){
x[i] = i;
//初始化排列
}
BackTrack(1, x, n);
printf("%d", sum);
}