回溯法,在以前也是会的,只不过没有系统的学习,只是从题目中接触了一些。
今天,系统的学习了一下,颇有感触。
对于回溯,书上的模版是这样的:
bool finished = FLASE; /*found all solutions yet? */ backtrack(int a[], int k, data input){ int c[MAXCANDIDATES]; // candidates for next position int ncandidates; //next position candidate count int i; //counter if(is_a_solution(a, k, input)) process_solution(a, k, input); else{ k = k+1; construct_candidates(a, k, input, c, &ncandidates); for(i=0; i<ncandidates; i++){ a[k] = c[i]; backtrack(a, k, input); if(finished) return ; //terminate early } } }
对于各个函数的解释:
is_a_solution(a, k, input):该函数测试向量a的前k个元素是否为一个完整解。最后一个参数input用来给这个函数传递一些其他信息。
construct_candidates(a, k, input, c, ncandidates): 该函数根据a的前k-1个元素值,把第k个元素的所有候选值放到数组c中。候选值的数量为ncandidates。
process_solution(a, k):当一个完整解呗构造出来后,对解进行处理。
请注意递归是如何优美地实现回溯算法的。递归调用的每层都有自己的候选数组c,因此每个位置上那个尚未考虑的候选集不会相互影响。
实例:
1.构造所有子集:
#include <stdio.h> #include <string.h> #include <stdlib.h> int n; void backtrack(int a[], int k){ int i; if(k == n){ printf("{"); for(i=1; i<=k; i++){ if(a[i] == 1) printf(" %d", i); } printf(" }\n"); } else{ k = k+1; a[k] = 1; backtrack(a, k); a[k] = 0; backtrack(a, k); } } int main(){ int a[30]; scanf("%d", &n); backtrack(a, 0);; return 0; }
2.构造所有排列:
#include <stdio.h> #define MAXN 20 void backtrack(int a[], int k, int n){ int i, ncadidates, c[MAXN]; unsigned char in_perm[MAXN]; if(k == n){ for(i=1; i<=n; i++) printf("%d ", a[i]); putchar('\n'); } else{ k = k+1; ncadidates = 0; for(i=1; i<=n; i++) in_perm[i] = 0; for(i=1; i<k; i++) in_perm[a[i]] = 1; for(i=1; i<=n; i++){ if(in_perm[i] == 0){ c[ncadidates] = i; ncadidates++; } } for(i=0; i<ncadidates; i++){ a[k] = c[i]; backtrack(a, k, n); } } } int main(){ int n, a[MAXN]; scanf("%d", &n); backtrack(a, 0, n); return 0; }
3.八皇后问题:
八皇后问题中每一行只能放置一个皇后,所以用一个一维数组就可以将排列位置存的下了。
对于数组a[i],下标i表示i行,a[i]表示列。
所以对于两个皇后,一定不在同一行,而:
1.是否在同一列,检查a[i]是否等于a[j],相等则在同一行,否则不在。
2.是否在一条斜线上,检查abs(i-j)是否等于abs(a[i]-a[j])。
如此代码便很好写了。
#include <stdio.h> #include <stdlib.h> #define MAXN 15 int solution_count; void construct_candidates(int a[], int k, int n, int c[], int *ncandidates){ int i, j; unsigned char legal_move; *ncandidates = 0; for(i=1; i<=n; i++){ legal_move = 1; for(j=1; j<k; j++){ if(abs(k-j) == abs(i-a[j])) legal_move = 0; //对角线 if(i == a[j]) legal_move = 0;//列 if(legal_move == 0) break; } if(legal_move == 1){ c[*ncandidates] = i; (*ncandidates)++; } } } void backtrack(int a[], int k, int n){ int c[MAXN]; int ncandidates, i; if(k == n){ solution_count++; } else{ k++; construct_candidates(a, k, n, c, &ncandidates); for(i=0; i<ncandidates; i++){ a[k] = c[i]; backtrack(a, k, n); } } } int main(){ int n, a[MAXN]; solution_count = 0; scanf("%d", &n); backtrack(a, 0, n); printf("%d\n", solution_count); return 0; }