算法篇-8-回溯法-N皇后&最优装载&01背包

来源:互联网 发布:帆布包推荐 知乎 编辑:程序博客网 时间:2024/06/10 12:50

本系列所有代码https://github.com/YIWANFENG/Algorithm-github

回溯法思想

回溯法运行起来类似于遍历,只不过会在遍历过程中去除一部分不可能的无效遍历()。

解决的问题的答案一般可以由一个向量表示,例如V= {x1,x2,x3....},其中x1,x2,x3...的取值便为最优解。

解空间即该问题所有可能的解的集合,在表示上分为子集树与排列树。

子集树即时该问题的x1,x2,x3....的取值影响最优解,而排列数是该问题的x1,x2,x3....的排列影响最优解。

在问题求解过程中,可以遍历整个树求最优解,但是一般情况我们都会用限界函数与剪枝函数对搜索加以限制,来节省我们的时间。

(相关名词。活结点:孩子未遍历完的结点 扩展结点:正在生成孩子的结点,

死结点:孩子遍历完毕的结点)

 

 

N皇后的安排问题

算法分析思路以及相关数学公式:

设x[i] 表示皇后i放在棋盘的第i行的第X[j]列。

则可由题目规则可知,任意两个X[I]互不相同。并且2皇后不可放于同一斜线上。

那么可得约束条件2,( i –j) != (x[i] – x[j] ) 同一斜线上的行相减与列相减结果相等。

算法可为,从第一行遍历,检查该行的某处是否可以安排皇后,是的话则进行下一行的遍历查找。等到找到一个解向量后向前回溯一层继续寻找,直到寻找完所有的解向量。

 

程序源代码:

#include <iostream>#include <stdlib.h>using namespace std;class NQueenSolver {private:       intn;       //皇后数量       int*x;     //每一行的皇后所在的列       intsum;   //当前解的数量      private:       boolValidate(int k) {              //验证第k行皇后位置是否合理              for(int i=1; i<k; ++i) {                     if((k-i)==abs(x[i]-x[k])|| x[i]==x[k])                            return false;              }              return true;       }       voidBacktrack(int t) {              if(t>n) {  //到达解空间树的叶子                      ++sum;                       if(sum==1)show_plan();              } else {                     //遍历所有的子结点                     for(inti=1; i<=n; ++i) {                            x[t] = i;                            if(Validate(t))                                   Backtrack(t+1);                     }              }       }       voidBacktrack_Iterative() {              int k =1 ; //处理层数              x[1] = 0;              while(k>0) {                     ++x[k];                     //遍历所有子结点,寻找满足约束的子结点                     while(x[k]<=n&& !Validate(k)) ++x[k];                     if(x[k]<=n){ //找到满足约束的子结点                            if(k==n) { //到达叶子结点                                    ++sum;                                   if(sum== 1) show_plan();                            } else {                                   //进入下一层结点                                   ++k;                                   x[k]= 0;                            }                     }else {                            //所有子结点遍历完毕,回溯                            --k;                     }                                                            }       }       voidshow_plan() {              for(int i=1; i<=n; ++i) {                     for(intj=1; j<=n; ++j) {                            if(x[i]==j) cout<<"Q ";                            else cout<<"* ";                     }                     cout<<endl;              }       }public:       intSolve(int num_queens) {              //num_queens皇后数量              //解的数量              n = num_queens;              sum = 0;              x = new int[n+1];              Backtrack(1);              delete []x;              return sum;       }       intSolve_iterative(int num_queens) {              //num_queens皇后数量              //解的数量              n = num_queens;              sum = 0;              x = new int[n+1];              Backtrack_Iterative();              delete []x;              return sum;       }};  int main(){             intnum = 0,sum;       cout<<"输入皇后数\n";       cin>> num;                    NQueenSolverqs;       sum= qs.Solve(num);       //sum= qs.Solve_iterative(num);       cout<<num<<"皇后问题解的数量:"<<sum<<endl;             return0;}


 

//结果正确。由于解数量过多,在这里只输出了一组解。

 

 

最优装载问题

题目:

最优装载问题

求解一该问题解向量,使得该装载的最大重量最大。

 

算法分析思路以及相关数学公式:

子集树表示问题的解空间。

完全遍历解空间,每一次遍历到叶子结点,查看此时解是否比已知解更优,若更优则更新解。

限界函数1:当前选择可以装入,即当前解不超过额定最大载重量

限界函数2:选择当前解后可以存在最优解,即当前选择完后加上所有后面等待选择的解大于最优解(即才可能存在最优解)。


程序源代码:

 

#include <iostream>#include <stdlib.h>#include <stack>using namespace std;template <class T_>class MaxLoading {private:// in  int n;const T_ *w; //每个集装箱重量 T_ c;  //可载重量 // outT_ bestw;//当前最优载重量 int *bestx;//当前最优解//interiorint *x;//当前解T_ cw;//当前载重量 T_ r; //当前剩余物品重量 void Backtrack(int i) {if(i>n) {//到达叶子bestw = cw;for(int j=1; j<=n; ++j) bestx[j] = x[j]; return ;}//更新剩余重量T_ r_backup = r;r -= w[i];//搜索左子节点 if(cw+w[i]<=c) {//约束函数 x[i] = 1;T_ cw_backup = cw;cw+=w[i];Backtrack(i+1);cw = cw_backup; }//搜索右子结点if(cw+r>bestw) { //限界函数 x[i] = 0;Backtrack(i+1);} r = r_backup;} public:int Solve(int n_,T_ c_,const T_ *w_,int *bestx_) {//n_ 物品数量//c_ 最大载重量// w_[] 物品重量表// bestx_[] 最优解 n = n_;c = c_;w = w_;bestx = bestx_;x =  new int[n+1];cw = 0;bestw = 0;r = 0;for(int i=1; i<=n; ++i) r+=w[i];Backtrack(1);delete [] x;return bestw;}};int main(){int n = 3,sum;MaxLoading<float> ml;float w[] = {0,10,40,40};int x[n+1];float bestw = ml.Solve(n,60.0,w,x);//float bestw = ml.Solve_Iterative(n,60.0,w,x);cout<<"最优值:"<<bestw<<endl<<"装载方式:\n";for(int i=1; i<=n; ++i) {cout<<x[i]<<' ';a}cin.get();return 0;}


0-1背包问题

算法分析思路以及相关数学公式:

此问题类似最优装载。在搜索子集树的解空间时,只要其做儿子结点是可行的一个结点,就进入左子树,当右子树可能包含最优解时才进入右子树,否则减去右子树。设r为当前剩余物品的价值总和,cp是当前价值,bestp是当前最优价值。当CP+r<=bestp时,可减去右子树。计算右子树中解的上界可以利用贪心选择法求背包问题的思想。

 

程序源代码:

#include <iostream>#include <stdlib.h>#include <algorithm> using namespace std; class Object {public:       intID;    //原始编号       floatd;   //单位重量的价值             booloperator< (const Object & a) const {              return d>=a.d;       }      };  template <class T_,class W_>class Knapsack {private:       //in        intn;       T_*w; //每个物品重量       W_*p; //每个物品价值 ,需要从大到小排序       T_c;              //背包可载重量             //out       W_bestp;       //当前最优载价值       int*bestx;      //当前最优解             //interior       int*x;            //当前解       T_cw;           //当前载重量       W_cp;          //当前价值  private:       W_Bound(int i) { //计算上界              T_ c_left = c- cw;              W_ b = cp;              while(i<=n &&w[i]<=c_left)   {                     c_left-= w[i];                     b+=p[i];                     ++i;              }              if(i<=n) b+=p[i]*c_left/w[i];              return b;       }           voidBacktrack(int i) {              if(i>n) {                     //到达叶子                     bestp= cp;                     for(intj=1; j<=n; ++j) bestx[j] = x[j];                     return;              }               //搜索左子节点              if(cw+w[i]<=c) {//约束函数                     x[i]= 1;                     //T_cw_backup = cw;                     //W_cp_backup = cp;                     cw+=w[i];                     cp+=p[i];                     Backtrack(i+1);                     cw-=w[i];                     cp-=p[i];                     //cw= cw_backup;                     //cp= cp_backup;              }              //搜索右子结点              if(Bound(i+1)>bestp) { //限界函数                     x[i]= 0;                     Backtrack(i+1);              }           }       /*voidBacktrack_Iterative(int i) {              //              int k = 1;              bool flag = true;              while(k>0) {                     //左子树                     if(flag&& cw+w[k]<=c) {//约束函数                            x[k] = 1;                            T_ cw_backup = cw;                            cw+=w[i];                            Backtrack(i+1);                            cw = cw_backup;                     }                                                                                 //右子树                      if(flag&& cw+r>bestw) { //限界函数                            x[i] = 0;                            Backtrack(i+1);                     }                                  }                   }*/public:       W_Solve(int n_,T_ c_,const T_ *w_,const W_ *p_,int *bestx_) {              //n_ 物品数量              //c_ 背包最大载重量              // w_[] 物品重量表              // p_[] 物品价值              // bestx_[] 最优解              //返回最优价值              n = n_;              c = c_;              w = new T_[n+1];              p = new W_[n+1];              bestx = bestx_;                           x = new int[n+1];              cw = 0;              cp = 0;              bestp = 0;              Object *Q = new Object[n];              T_ w_total = 0; //总重量              W_ p_total = 0; //总价值                           for(int i=1; i<=n; i++) {                     Q[i-1].ID= i;                     Q[i-1].d= p_[i]/(float)w_[i]; ////!!!                     w_total+= w_[i];                     p_total+= p_[i];              }              //检查是否全可装下              if(w_total < c) {                     delete[] Q;                     for(inti=1; i<=n; ++i) x[i] = 1;                     returnp_total;              }                           sort(Q,Q+n); //物品按单位价值从大到小排序 ,以便Bound()              /*for(int i=0;i<n;++i)              {                     cout<<Q[i].ID<<''<<Q[i].d<<endl;              }*/              //一般情况              for(int i=1; i<=n; ++i) {                     p[i]= p_[ Q[i-1].ID ];                     w[i]= w_[ Q[i-1].ID ];              }              /*for(int i=1;i<=n;++i)              {                     cout<<p[i]<<''<<w[i]<<endl;              }*/              Backtrack(1);                           //最优解              for(int i=1; i<=n; ++i)                     x[Q[i-1].ID]= bestx[i];              for(int i=1; i<=n; ++i)                     bestx_[i]= x[i];                                  delete [] p;              delete [] w;              delete [] x;              return bestp;       }      };   int main(){             intn = 4,sum;       Knapsack<int,float>ml;       intw[] = {0,3,5,2,1};       floatp[] = {0,9,10,7,4};       intx[n+1];       floatbestp = ml.Solve(n,7,w,p,x);             cout<<"最优值:"<<bestp<<endl<<"装载方式:\n";       for(inti=1; i<=n; ++i) {              cout<<x[i]<<' ';       }             cin.get();       return0;}


 

 

 

0 0