回溯法原理及应用

 

回溯的基本原理:

在问题的解空间中,按深度优先遍历策略,从根节点出发搜索解空间树。算法搜索至解空间的任意一个节点时,先判断该节点是否包含问题的解。如果肯定不包含,跳过对以该节点为根的子树的搜索,逐层向其祖先节点回溯,否则进入该子树,继续深度优先搜索。

 

回溯法解问题的所有解时,必须回溯到根节点,且根节点的所有子树都被搜索后才结束。回溯法解问题的一个解时,只要搜索到问题的一个解就可结束。

 

回溯的基本步骤:

1.定义问题的解空间

2.确定易于搜索的解空间结构

3.以深度优先搜索的策略搜索解空间,并在搜索过程中用剪枝函数避免无效搜索

 

典型的解空间:

1.子集树:从n个元素的集合S中找到S满足某种性质的子集,解空间是二叉树结构

2.排列树:确定n个元素满足某种性质的排列,解空间是n叉树

 

提高回溯法的效率:

1.约束函数:减去不满足约束的子树

2.限界函数:剪去得不到最优解的子树

 

回溯的基本结构:递归、非递归迭代

 

回溯的基本框架:

1.回溯法搜索子集树

void backtrack(int i){

{

     if(i > n) output(x);

     else{

          for(int i=0; i<=1; i++){

                  x[t] = i;

                  if(constraint(x[t]) && bound(x)) backtrack(i+1);

          }

     }

}

 

2.回溯法搜索排列树

void backtrack(int t){

     if(t > n) output(t);

     else{

          for(int i=t; i<=n; i++){

                  swap(x[t],x[i]);

                  if(constraint(t) && bound(t)) backtrack(i+1);

                  swap(x[t],x[i]);

          }

     }

}

 

回溯的几个应用:

1.n皇后问题

//n皇后问题

#include<stdio.h>

#include<stdlib.h>

#include<math.h>

 

#define NUM 8

 

//判断第t个皇后是否与1-(t-1)个皇后冲突

bool place(int x[NUM], int t){

     int k = 0;

     for(int k=1; k<t; k++){

             if(x[k] == x[t] || abs(t-k) == abs(x[t]-x[k])){

                     return false;

             }

     }

     return true;

}

 

//状态空间是:第1个皇后在第1行的所有列,第2个皇后在第2行的所有列。。。

void back(int i, int x[NUM]){

     int j = 1;

     if(i > NUM){

          for(j=1; j<=NUM; j++){

                   printf(“%d “,x[j]);

          }

          printf(“\n”);

          return;

     }

    

     for(j=1; j<=NUM; j++){

              x[i] = j;

              if(place(x,i)){

                          back(i+1,x);

              }

     }

}

 

int main(){

   

    int x[NUM];

    

    back(1,x);

   

    system(“pause”);

    return 0;

}

 

2.装载问题

//装载问题

#include<stdio.h>

#include<stdlib.h>

 

int cw = 0;

int bestcw = 0;

int total = 0; 

int n;

int w[10];

 

void backtrack(int i){

     if(i > n){

          if(bestcw < cw){

                    bestcw = cw;

          }

          return;

     }

    

     //遍历左子树,1,包含w[i]

     if(w[i] + cw <= total){

             cw += w[i];

             backtrack(i+1);  

             cw -= w[i];

     }

    

     //遍历右子树,0,不包含w[i]

     //cw不变

     backtrack(i+1);

}

 

int main(){

 

  int i;

  scanf(“%d”,&total);

  scanf(“%d”,&n);

  for(i=1; i<=n; i++){

           scanf(“%d”,&w[i]);

  }

  backtrack(1);

  printf(“%d\n”,bestcw);

   

  system(“pause”);

  return 0;  

   

 

3.排列问题

//求1-n的排列

#include<stdio.h>

#include<stdlib.h>

 

#define NUM 4

int x[NUM];

 

void swap(int &a, int &b){

     int c = a;

     a = b;

     b = c;

}

 

//回溯法遍历排列树,同时分治

void perm(int i){

    int j;

    if(i > NUM){

         for(j=1; j<=NUM; j++){

                  printf(“%d “,x[j]);

         }

         printf(“\n”);

         return;

    }

   

    for(j=i; j<=NUM; j++){

             swap(x[i],x[j]);

             perm(i+1);

             swap(x[i],x[j]);  //还原

    }

}

 

int main(){

 

  int i=1;

  for(i=1; i<=NUM; i++){

           x[i] = i;

  }

 

  perm(1); 

   

  system(“pause”);

  return 0;  

   

}

 

回溯法心得:

1、全局变量在回溯中,注意在backtrack(i+1)返回时,修改的恢复;若将变量或者数组放在数组中传递时,在backtrack(i+1)返回时,栈会恢复,不用自行恢复。

2、递归、回溯的思考过程中,思维不要太细,要抽象,否则很容易混乱

3、步骤、框架、模型,成体系

4、回溯的效率是o(2^n)或者o(n!),虽然回溯较通用,但是效率较低,尽可能剪枝,以提高效率。非递归的回溯能减少递归的o(n)的栈,但是比较难写。

 

    原文作者:回溯法
    原文地址: https://blog.csdn.net/gls_jia/article/details/6777701
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞