回溯法(挑战编程)

回溯法,在以前也是会的,只不过没有系统的学习,只是从题目中接触了一些。

今天,系统的学习了一下,颇有感触。

对于回溯,书上的模版是这样的:

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;
}
原文地址:https://www.cnblogs.com/tanhehe/p/3000984.html