假设现在有一列数a[0],a[1], ...a[n-1]
①如果一个问题的解的长度不是固定的,并且解和元素顺序无关,即可以从中选择0个或多个,那么解空间的个数将是指数级别的,为2^n,可以用下面的子集树来表示所有的解(假设这里n=4)
PIC. 子集树
子集树的算法框架为
void backtrack(int t) {//表示访问到第t层,t从0开始
if (t == n) //如上图(PIC. 子集树)n = 4的时候就可以输出解了
output(x);
else
for (int i = 0; i <= l; i++) { //表示选或不选a[t]
x[t] = i;
if (constraint(t) && bound(t))
backtrack(t + 1);
}
}
②如果解空间是由n个元素的排列形成,也就是说n个元素的每一个排列都是解空间中的一个元素,那么,最后解空间的组织形式是排列树
PIC.排列树
排列树算法的基本框架为
模板一
void backtrack(int t)
{
if (t == n)
output(x);
else
for (int i = t; i < n; i++)
{
swap(x[t], x[i]);
if (constraint(t) && bound(t))
{
backtrack(t + 1);
swap(x[t], x[i]);
}
}
}
排列数举例:求1,2,3,4,5所有的排列
#include
using namespace std;
int x[5] = {1, 2, 3, 4, 5};
int n = 5;
void swap(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
void BackTrack(int t) {
if (t == n) {
for (int i = 0; i < n; i++)
cout << x[i] << " ";
cout << endl;
}
else
for (int i = t; i < n; i++) {
swap(x[t], x[i]);
BackTrack(t + 1);
swap(x[t], x[i]);
}
}
int main() {
BackTrack(0);
return 0;
}
变种:求解满足a-b+c-d+e = m的一个排列,找到一个就返回
#include
using namespace std;
int x[5] = {1, 2, 3, 4, 5};
int n = 5;
int m;
void swap(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
bool BackTrack(int t) {
if (t == n) {
int sum = 0;
int flag = 1;
for (int i = 0; i < n; i++) {
sum += x[i] * flag;
flag = -flag;
}
if (sum == m) return true;
}
else
for (int i = t; i < n; i++) {
swap(x[t], x[i]);
if (BackTrack(t + 1))
return true;
swap(x[t], x[i]);
}
return false;
}
int main() {
m = 1;
if(BackTrack(0)) {
cout << "找到排列满足条件: ";
for (int i = 0; i < n; i++) {
cout << x[i] << " ";
}
cout << endl;
} else {
cout << "不存在这样的排列" << endl;
}
return 0;
}
遇到子集树和排列数的回溯问题,几乎都可以用上面的模板来套
续:排列树的模板相对于子集树来说比较难理解,我也是一直没有深入去理解它的结构,但是,自从看了LRJ的《算法竞赛入门经典》之后关于排列的创建,突然觉得眼前一亮,真的是非常容易理解,伪代码如下
注意: 序列A表示“前缀”序列,以便输出,S表示需要进行全排列的元素集合,以便依次选做第一个元素
void print_emu(序列 A, 集合S) {
if (序列A满了) 输出序列A;
else {
1.按照从小到大依次考虑S中的每一个元素v
2.print_emu(在A的末尾加上v后得到新的序列, S-{v})
}
}
以上代码可以按字典输出n个数的全排列
c++代码如下
模板二
#include
using namespace std;
//cur表示当前序列A中的元素为A[0], A[1],...A[cur-1]
int print_emu(int A[], int n, int cur) {
if (cur == n) {
for (int i = 0; i < n; i++)
cout << A[i] << " ";
cout << endl;
} else for (int i = 1; i <= n; i++) {//从小到大依次考虑S中的每一个元素,S中的元素表示集合{1..n}除A以外的元素
int ok = 1;
for (int j = 0; j < cur; j++)
if (A[j] == i) ok = 0; //表示i在A中出现过,那么就不考虑该元素了
if (ok) {
A[cur] = i;
print_emu(A, n, cur+1);
}
}
}
int main() {
int x[5];
print_emu(A, 5, 0);
return 0;
}
利用模板一的代码
#include
using namespace std;
char x[12];
char s[13];
int n;
bool flag = false;
void swap(int &a,int &b) {
int tmp =a;
a = b;
b = tmp;
}
int sqr(char a, int n) {
if (n == 1)
return a - 'A' + 1;
return (a - 'A' + 1)* sqr(a, n - 1);
}
void Backtrack(int t) {
int i;
if(t == 5) {
if (sqr(x[0], 1) - sqr(x[1], 2) + sqr(x[2], 3)- sqr(x[3], 4) + sqr(x[4], 5) == n) {
flag = true;
for (int i = 0; i < 5;i++)
cout << x[i];
cout << endl;
}
return;
}
if (flag) //表示已经有结果了
return;
for(i = t;i < strlen(s);i++) {
swap(s[t],s[i]);
x[t] = s[t];
Backtrack(t+1);
swap(s[i],s[t]);
}
}
int cmp(const void *a, const void *b) {
return *((char*)b) - *((char*)a);
}
int main() {
while (cin >> n >> s && !(n == 0 && strcmp(s, "END") == 0)) {
flag = false;
qsort(s, strlen(s), sizeof(s[0]), cmp);
Backtrack(0);
if (!flag) {
cout << "no solution" << endl;
}
}
return 0;
}
①x数组保存的是a数组的索引
#include
using namespace std;
int sqr(char a, int n) {
if (n == 1)
return a - 'A' + 1;
return (a - 'A' + 1)* sqr(a, n - 1);
}
int cmp(const void *a, const void *b) {
return *((char*)b) - *((char*)a);
}
bool print_emu(char a[], int x[], int n, int cur, int target) {
if (cur == 5) {
if (sqr(a[x[0] - 1], 1) - sqr(a[x[1]-1], 2) + sqr(a[x[2]-1], 3) - sqr(a[x[3]-1], 4) + sqr(a[x[4]-1], 5) == target)
return true;
} else for (int i = 1; i <= n; i++) {
int ok = 1;
for (int j = 0; j < cur; j++)
if (x[j] == i) ok = 0;
if (ok) {
x[cur] = i;
if (print_emu(a, x, n, cur+1, target))
return true;
}
}
return false;
}
int main() {
int x[13], target;
char a[13];
while (cin >> target >> a && !(target == 0 && strcmp(a, "END") == 0)) {
qsort(a, strlen(a), sizeof(a[0]), cmp);
if (print_emu(a, x, strlen(a), 0, target)) {
for (int i = 0; i < 5; i++)
cout << a[x[i]-1];
cout << endl;
} else {
cout << "no solution" << endl;
}
}
return 0;
}
②x数组保存的是a数组中的字符串
#include
using namespace std;
int sqr(char a, int n) {
if (n == 1)
return a - 'A' + 1;
return (a - 'A' + 1)* sqr(a, n - 1);
}
int cmp(const void *a, const void *b) {
return *((char*)b) - *((char*)a);
}
bool print_emu(char a[], char x[], int n, int cur, int target) {
if (cur == 5) {
if (sqr(x[0], 1) - sqr(x[1], 2) + sqr(x[2], 3) - sqr(x[3], 4) + sqr(x[4], 5) == target)
return true;
} else for (int i = 0; i < n; i++) {
int ok = 1;
for (int j = 0; j < cur; j++)
if (x[j] == a[i]) ok = 0;
if (ok) {
x[cur] = a[i];
if (print_emu(a, x, n, cur+1, target))
return true;
}
}
return false;
}
int main() {
char x[13];
int target;
char a[13];
while (cin >> target >> a && !(target == 0 && strcmp(a, "END") == 0)) {
qsort(a, strlen(a), sizeof(a[0]), cmp);
if (print_emu(a, x, strlen(a), 0, target)) {
for (int i = 0; i < 5; i++)
cout << x[i];
cout << endl;
} else {
cout << "no solution" << endl;
}
}
return 0;
}