回溯演算法:子集樹和排列樹
阿新 • • 發佈:2019-02-05
假設現在有一列數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 <iostream> 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 <iostream>
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 <iostream>
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;
}
利用以上兩個全排列模板,輕鬆KO掉poj1015
利用模板一的程式碼
#include <iostream>
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 <iostream>
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 <iostream>
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;
}