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

本系列所有代码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; 		 //可载重量 
	
	// out
	T_ bestw;	//当前最优载重量 
	int *bestx;	//当前最优解
	
	//interior
	int *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;
}

 

 

 

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