【回溯法】n后问题和0-1背包问题

1、n后问题                                                                  转自博主:http://blog.csdn.net/liufeng_king/article/details/8774394

    问题描述:在n×n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于在n×n格的棋盘上放置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。

《【回溯法】n后问题和0-1背包问题》

     问题解析:用n元数组x[1:n]表示n后问题的解。其中,x[i]表示皇后i放在棋盘的第i行的第x[i]列。由于不允许将2个皇后放在同一列上,所以解向量中的x[i]互不相同。如果将n*n的棋盘看做是二维方阵,其行号从上到下,列号从左到右依次编号为1,2,……n。设两个皇后的坐标分别为(i,j)和(k,l)。若两个皇后在同一斜线上,那么这两个皇后的坐标连成的线为1或者-1。因此有:

《【回溯法】n后问题和0-1背包问题》

     由此约束条件剪去不满足行、列和斜线约束的子树。程序的递归回溯实现如下:

[cpp] 
view plain
 copy

  1. //n后问题 回溯法计算 递归  
  2. #include “stdafx.h”  
  3. #include <iostream>  
  4. #include “math.h”  
  5. using namespace std;   
  6.   
  7. class Queen  
  8. {  
  9.    friend int nQueen(int);  
  10.    private:  
  11.       bool Place(int k);  
  12.       void Backtrack(int t);  
  13.       int  n,    // 皇后个数  
  14.           *x;    // 当前解  
  15.       long sum;  // 当前已找到的可行方案数    
  16. };   
  17.   
  18. int main()  
  19. {  
  20.     int n=4,m;  
  21.     cout<<n<<“皇后问题的解为:”<<endl;  
  22.     m=nQueen(n);  
  23.     cout<<n<<“皇后问题共有”;  
  24.     cout<<m<<“个不同的解!”<<endl;  
  25.     return 0;  
  26. }  
  27.   
  28. bool Queen::Place(int k)  
  29. {  
  30.     for (int j=1;j<k;j++)  
  31.     {  
  32.         if ((abs(k-j)==abs(x[j]-x[k]))||(x[j]==x[k]))   
  33.         {  
  34.             return false;  
  35.         }  
  36.     }  
  37.     return true;  
  38. }   
  39.   
  40. void Queen::Backtrack(int t)//t扩展的是行  
  41. {  
  42.     if (t>n)  
  43.     {  
  44.         sum++;  
  45.         for (int i=1;i<=n;i++)  
  46.         {  
  47.             cout<<x[i]<<” “;  
  48.         }  
  49.         cout<<endl;  
  50.     }  
  51.     else  
  52.     {  
  53.         //探索第t行的每一列是否有元素满足要求  
  54.         for (int i=1;i<=n;i++)  
  55.         {  
  56.             x[t]=i;  
  57.             if (Place(t))  
  58.             {  
  59.                 Backtrack(t+1);  
  60.             }  
  61.         }  
  62.     }  
  63.  }  
  64.   
  65. int nQueen(int n)  
  66. {  
  67.     Queen X;  
  68.     X.n=n;  
  69.     X.sum=0;  
  70.   
  71.     int *p=new int[n+1];  
  72.   
  73.     for(int i=0;i<=n;i++)  
  74.     {  
  75.         p[i]=0;  
  76.     }  
  77.   
  78.     X.x=p;  
  79.     X.Backtrack(1);  
  80.   
  81.     delete []p;  
  82.     return X.sum;  
  83. }  

     数组x记录了解空间树中从根到当前扩展节点的路径,这些信息包含了回溯法在回溯是所需要的信息。利用数组x所含的信息,可将上述回溯法表示成
非递归
的形式。进一步省去O(n)递归栈空间。
迭代
实现的n后问题具体代码如下:

[cpp] 
view plain
 copy

  1. //n后问题 回溯法计算 迭代  
  2. #include “stdafx.h”  
  3. #include <iostream>  
  4. #include “math.h”  
  5. using namespace std;   
  6.   
  7. class Queen  
  8. {  
  9.    friend int nQueen(int);  
  10.    private:  
  11.       bool Place(int k);  
  12.       void Backtrack(void);  
  13.       int  n,    // 皇后个数  
  14.           *x;    // 当前解  
  15.       long sum;  // 当前已找到的可行方案数    
  16. };   
  17.   
  18. int main()  
  19. {  
  20.     int n=4,m;  
  21.     cout<<n<<“皇后问题的解为:”<<endl;  
  22.     m=nQueen(n);  
  23.     cout<<n<<“皇后问题共有”;  
  24.     cout<<m<<“个不同的解!”<<endl;  
  25.     return 0;  
  26. }  
  27.   
  28. bool Queen::Place(int k)  
  29. {  
  30.     for (int j=1;j<k;j++)  
  31.     {  
  32.         if ((abs(k-j)==abs(x[j]-x[k]))||(x[j]==x[k]))   
  33.         {  
  34.             return false;  
  35.         }  
  36.     }  
  37.     return true;  
  38. }   
  39.   
  40. void Queen::Backtrack()  
  41. {  
  42.     x[1] = 0;  
  43.     int k = 1;  
  44.     while(k>0)  
  45.     {  
  46.         x[k] += 1;  
  47.         while((x[k]<=n)&&!(Place(k)))//寻找能够放置皇后的位置  
  48.         {  
  49.             x[k] += 1;  
  50.         }  
  51.   
  52.         if(x[k]<=n)//找到位置  
  53.         {  
  54.             if(k == n)  
  55.             {  
  56.                 for (int i=1;i<=n;i++)  
  57.                 {  
  58.                     cout<<x[i]<<” “;  
  59.                 }  
  60.                 cout<<endl;  
  61.                 sum++;  
  62.             }  
  63.             else  
  64.             {  
  65.                 k++;  
  66.                 x[k]=0;  
  67.             }  
  68.         }  
  69.         else  
  70.         {  
  71.             k–;  
  72.         }  
  73.     }  
  74.  }  
  75.   
  76. int nQueen(int n)  
  77. {  
  78.     Queen X;  
  79.     X.n=n;  
  80.     X.sum=0;  
  81.   
  82.     int *p=new int[n+1];  
  83.   
  84.     for(int i=0;i<=n;i++)  
  85.     {  
  86.         p[i]=0;  
  87.     }  
  88.   
  89.     X.x=p;  
  90.     X.Backtrack();  
  91.   
  92.     delete []p;  
  93.     return X.sum;  
  94. }  

    程序运行结果如图:

《【回溯法】n后问题和0-1背包问题》
     

     2、0-1背包问题

     问题描述  

     给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包的容量为C。问:应如何选择装入背包的物品,使得装入背包中物品的总价值最大?

     形式化描述:给定c >0, wi >0, vi >0 , 1≤i≤n.要求找一n元向量(x1,x2,…,xn,), xi∈{0,1}, ∋ ∑ wi xi≤c,且∑ vi xi达最大.即一个特殊的整数规划问题。

      问题解析:0-1背包问题是子集选取问题。0-1 背包问题的解空间可以用子集树表示。在搜索解空间树时,只要其左儿子节点是一个可行节点,搜索就进入左子树。当右子树中有可能含有最优解时,才进入右子树搜索。否则,将右子树剪去。设r是当前剩余物品价值总和,cp是当前价值;bestp是当前最优价值。当cp+r<=bestp时,可剪去右子树。计算右子树上界的更好的方法是将剩余物品依次按其单位价值排序,然后依次装入物品,直至装不下时,再装入物品一部分而装满背包。

     例如:对于0-1背包问题的一个实例,n=4,c=7,p=[9,10,7,4],w=[3,5,2,1]。这4个物品的单位重量价值分别为[3,2,3,5,4]。以物品单位重量价值的递减序装入物品。先装入物品4,然后装入物品3和1.装入这3个物品后,剩余的背包容量为1,只能装0.2的物品2。由此得一个解为[1,0.2,1,1],其相应价值为22。尽管这不是一个可行解,但可以证明其价值是最优值的上界。因此,对于这个实例,最优值不超过22。

     在实现时,由Bound计算当前节点处的上界。类Knap的数据成员记录解空间树中的节点信息,以减少参数传递调用所需要的栈空间。在解空间树的当前扩展节点处,仅要进入右子树时才计算上界Bound,以判断是否可将右子树剪去。进入左子树时不需要计算上界,因为上界预期父节点的上界相同。算法的具体实现如下:

[cpp] 
view plain
 copy

  1. //0-1背包问题 回溯法求解  
  2. #include “stdafx.h”  
  3. #include <iostream>  
  4. using namespace std;   
  5.   
  6. template<class Typew,class Typep>  
  7. class Knap  
  8. {  
  9.     template<class Typew,class Typep>  
  10.     friend Typep Knapsack(Typep [],Typew [],Typew,int);  
  11.     private:  
  12.         Typep Bound(int i);  
  13.         void Backtrack(int i);  
  14.   
  15.         Typew c;    //背包容量  
  16.         int n;      //物品数  
  17.   
  18.         Typew *w;   //物品重量数组  
  19.         Typep *p;   //物品价值数组  
  20.         Typew cw;   //当前重量  
  21.   
  22.         Typep cp;   //当前价值  
  23.         Typep bestp;//当前最后价值  
  24. };  
  25.   
  26. template<class Typew,class Typep>  
  27. Typep Knapsack(Typep p[],Typew w[],Typew c,int n);  
  28.   
  29. template <class Type>  
  30. inline void Swap(Type &a,Type &b);  
  31.   
  32. template<class Type>  
  33. void BubbleSort(Type a[],int n);  
  34.   
  35. int main()  
  36. {  
  37.     int n = 4;//物品数  
  38.     int c = 7;//背包容量  
  39.     int p[] = {0,9,10,7,4};//物品价值 下标从1开始  
  40.     int w[] = {0,3,5,2,1};//物品重量 下标从1开始  
  41.   
  42.     cout<<“背包容量为:”<<c<<endl;  
  43.     cout<<“物品重量和价值分别为:”<<endl;  
  44.   
  45.     for(int i=1; i<=n; i++)  
  46.     {  
  47.         cout<<“(“<<w[i]<<“,”<<p[i]<<“) “;  
  48.     }  
  49.     cout<<endl;  
  50.   
  51.     cout<<“背包能装下的最大价值为:”<<Knapsack(p,w,c,n)<<endl;  
  52.     return 0;  
  53. }  
  54.   
  55. template<class Typew,class Typep>  
  56. void Knap<Typew,Typep>::Backtrack(int i)  
  57. {  
  58.     if(i>n)//到达叶子节点  
  59.     {  
  60.         bestp = cp;  
  61.         return;  
  62.     }  
  63.   
  64.     if(cw + w[i] <= c)//进入左子树  
  65.     {  
  66.         cw += w[i];  
  67.         cp += p[i];  
  68.         Backtrack(i+1);  
  69.         cw -= w[i];  
  70.         cp -= p[i];  
  71.     }  
  72.   
  73.     if(Bound(i+1)>bestp)//进入右子树  
  74.     {  
  75.         Backtrack(i+1);  
  76.     }  
  77. }  
  78.   
  79. template<class Typew, class Typep>  
  80. Typep Knap<Typew, Typep>::Bound(int i)// 计算上界  
  81. {  
  82.     Typew cleft = c – cw;  // 剩余容量  
  83.     Typep b = cp;  
  84.   
  85.     // 以物品单位重量价值递减序装入物品  
  86.     while (i <= n && w[i] <= cleft)   
  87.     {  
  88.         cleft -= w[i];  
  89.         b += p[i];  
  90.         i++;  
  91.     }  
  92.   
  93.    // 装满背包  
  94.    if (i <= n)  
  95.    {  
  96.        b += p[i]/w[i] * cleft;  
  97.    }  
  98.   
  99.    return b;  
  100. }  
  101.   
  102. class Object  
  103. {  
  104.     template<class Typew,class Typep>  
  105.     friend Typep Knapsack(Typep[],Typew [],Typew,int);  
  106.     public:  
  107.         int operator <= (Object a)const  
  108.         {  
  109.             return (d>=a.d);  
  110.         }  
  111.     private:  
  112.         int ID;  
  113.         float d;      
  114. };  
  115.   
  116. template<class Typew,class Typep>  
  117. Typep Knapsack(Typep p[],Typew w[],Typew c,int n)  
  118. {  
  119.     //为Knap::Backtrack初始化  
  120.     Typew W = 0;  
  121.     Typep P = 0;  
  122.   
  123.     Object *Q = new Object[n];  
  124.     for(int i=1; i<=n; i++)  
  125.     {  
  126.         Q[i-1].ID = i;  
  127.         Q[i-1].d = 1.0 * p[i]/w[i];  
  128.         P += p[i];  
  129.         W += w[i];  
  130.     }  
  131.   
  132.     if(W <= c)//装入所有物品  
  133.     {  
  134.         return P;  
  135.     }  
  136.   
  137.     //依物品单位重量价值排序  
  138.     BubbleSort(Q,n);  
  139.   
  140.     Knap<Typew,Typep> K;  
  141.     K.p = new Typep[n+1];  
  142.     K.w = new Typew[n+1];  
  143.   
  144.     for(int i=1; i<=n; i++)  
  145.     {  
  146.         K.p[i] = p[Q[i-1].ID];  
  147.         K.w[i] = w[Q[i-1].ID];  
  148.     }  
  149.   
  150.     K.cp = 0;  
  151.     K.cw = 0;  
  152.     K.c = c;  
  153.     K.n = n;  
  154.     K.bestp = 0;  
  155.   
  156.     //回溯搜索  
  157.     K.Backtrack(1);  
  158.   
  159.     delete []Q;  
  160.     delete []K.w;  
  161.     delete []K.p;  
  162.     return K.bestp;  
  163. }  
  164.   
  165. template<class Type>  
  166. void BubbleSort(Type a[],int n)  
  167. {  
  168.      //记录一次遍历中是否有元素的交换     
  169.      bool exchange;    
  170.      for(int i=0; i<n-1;i++)    
  171.      {    
  172.         exchange = false ;    
  173.         for(int j=i+1; j<=n-1; j++)    
  174.         {    
  175.             if(a[j]<=a[j-1])    
  176.             {    
  177.                 Swap(a[j],a[j-1]);   
  178.                 exchange = true;    
  179.             }     
  180.         }     
  181.         //如果这次遍历没有元素的交换,那么排序结束     
  182.         if(false == exchange)    
  183.         {  
  184.              break ;    
  185.         }  
  186.      }  
  187. }  
  188.   
  189. template <class Type>  
  190. inline void Swap(Type &a,Type &b)  
  191. {  
  192.     Type temp = a;  
  193.     a = b;  
  194.     b = temp;  
  195. }  

     计算上界需要O(n)时间,在最坏情况下有O(2^n)个右儿子节点需要计算上界,故解0-1背包问题的回溯算法所需要的计算时间为O(n2^n)。程序运行结果如图:

《【回溯法】n后问题和0-1背包问题》

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