0029算法笔记——【回溯法】n后问题和0-1背包问题

1、n后问题

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

0029算法笔记——【回溯法】n后问题和0-1背包问题_第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。因此有:

0029算法笔记——【回溯法】n后问题和0-1背包问题_第2张图片

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

[cpp] view plain copy print ?
  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. }  
//n后问题 回溯法计算 递归
#include "stdafx.h"
#include <iostream>
#include "math.h"
using namespace std; 

class Queen
{
   friend int nQueen(int);
   private:
      bool Place(int k);
	  void Backtrack(int t);
      int  n,    // 皇后个数
          *x;    // 当前解
      long sum;  // 当前已找到的可行方案数  
}; 

int main()
{
	int n=4,m;
	cout<<n<<"皇后问题的解为:"<<endl;
	m=nQueen(n);
    cout<<n<<"皇后问题共有";
	cout<<m<<"个不同的解!"<<endl;
	return 0;
}

bool Queen::Place(int k)
{
	for (int j=1;j<k;j++)
	{
		if ((abs(k-j)==abs(x[j]-x[k]))||(x[j]==x[k])) 
		{
			return false;
		}
	}
	return true;
} 

void Queen::Backtrack(int t)//t扩展的是行
{
	if (t>n)
	{
		sum++;
		for (int i=1;i<=n;i++)
		{
		    cout<<x[i]<<" ";
		}
		cout<<endl;
	}
	else
	{
		//探索第t行的每一列是否有元素满足要求
		for (int i=1;i<=n;i++)
		{
			x[t]=i;
			if (Place(t))
			{
				Backtrack(t+1);
			}
		}
	}
 }

int nQueen(int n)
{
	Queen X;
	X.n=n;
	X.sum=0;

	int *p=new int[n+1];

	for(int i=0;i<=n;i++)
	{
		p[i]=0;
	}

	X.x=p;
	X.Backtrack(1);

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

[cpp] view plain copy print ?
  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后问题 回溯法计算 迭代
#include "stdafx.h"
#include <iostream>
#include "math.h"
using namespace std; 

class Queen
{
   friend int nQueen(int);
   private:
      bool Place(int k);
	  void Backtrack(void);
      int  n,    // 皇后个数
          *x;    // 当前解
      long sum;  // 当前已找到的可行方案数  
}; 

int main()
{
	int n=4,m;
	cout<<n<<"皇后问题的解为:"<<endl;
	m=nQueen(n);
    cout<<n<<"皇后问题共有";
	cout<<m<<"个不同的解!"<<endl;
	return 0;
}

bool Queen::Place(int k)
{
	for (int j=1;j<k;j++)
	{
		if ((abs(k-j)==abs(x[j]-x[k]))||(x[j]==x[k])) 
		{
			return false;
		}
	}
	return true;
} 

void Queen::Backtrack()
{
	x[1] = 0;
	int k = 1;
	while(k>0)
	{
		x[k] += 1;
		while((x[k]<=n)&&!(Place(k)))//寻找能够放置皇后的位置
		{
			x[k] += 1;
		}

		if(x[k]<=n)//找到位置
		{
			if(k == n)
			{
				for (int i=1;i<=n;i++)
				{
					cout<<x[i]<<" ";
				}
				cout<<endl;
				sum++;
			}
			else
			{
				k++;
				x[k]=0;
			}
		}
		else
		{
			k--;
		}
	}
 }

int nQueen(int n)
{
	Queen X;
	X.n=n;
	X.sum=0;

	int *p=new int[n+1];

	for(int i=0;i<=n;i++)
	{
		p[i]=0;
	}

	X.x=p;
	X.Backtrack();

	delete []p;
	return X.sum;
}
    程序运行结果如图:


     

     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 print ?
  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. }  
//0-1背包问题 回溯法求解
#include "stdafx.h"
#include <iostream>
using namespace std; 

template<class Typew,class Typep>
class Knap
{
	template<class Typew,class Typep>
	friend Typep Knapsack(Typep [],Typew [],Typew,int);
	private:
		Typep Bound(int i);
		void Backtrack(int i);

		Typew c;	//背包容量
		int n;		//物品数

		Typew *w;	//物品重量数组
		Typep *p;	//物品价值数组
		Typew cw;	//当前重量

		Typep cp;	//当前价值
		Typep bestp;//当前最后价值
};

template<class Typew,class Typep>
Typep Knapsack(Typep p[],Typew w[],Typew c,int n);

template <class Type>
inline void Swap(Type &a,Type &b);

template<class Type>
void BubbleSort(Type a[],int n);

int main()
{
	int n = 4;//物品数
	int c = 7;//背包容量
	int p[] = {0,9,10,7,4};//物品价值 下标从1开始
	int w[] = {0,3,5,2,1};//物品重量 下标从1开始

	cout<<"背包容量为:"<<c<<endl;
	cout<<"物品重量和价值分别为:"<<endl;

	for(int i=1; i<=n; i++)
	{
		cout<<"("<<w[i]<<","<<p[i]<<") ";
	}
	cout<<endl;

	cout<<"背包能装下的最大价值为:"<<Knapsack(p,w,c,n)<<endl;
	return 0;
}

template<class Typew,class Typep>
void Knap<Typew,Typep>::Backtrack(int i)
{
	if(i>n)//到达叶子节点
	{
		bestp = cp;
		return;
	}

	if(cw + w[i] <= c)//进入左子树
	{
		cw += w[i];
		cp += p[i];
		Backtrack(i+1);
		cw -= w[i];
		cp -= p[i];
	}

	if(Bound(i+1)>bestp)//进入右子树
	{
		Backtrack(i+1);
	}
}

template<class Typew, class Typep>
Typep Knap<Typew, Typep>::Bound(int i)// 计算上界
{
	Typew cleft = c - cw;  // 剩余容量
	Typep b = cp;

	// 以物品单位重量价值递减序装入物品
	while (i <= n && w[i] <= cleft) 
	{
		cleft -= w[i];
		b += p[i];
		i++;
	}

   // 装满背包
   if (i <= n)
   {
	   b += p[i]/w[i] * cleft;
   }

   return b;
}

class Object
{
	template<class Typew,class Typep>
	friend Typep Knapsack(Typep[],Typew [],Typew,int);
	public:
		int operator <= (Object a)const
		{
			return (d>=a.d);
		}
	private:
		int ID;
		float d;	
};

template<class Typew,class Typep>
Typep Knapsack(Typep p[],Typew w[],Typew c,int n)
{
	//为Knap::Backtrack初始化
	Typew W = 0;
	Typep P = 0;

	Object *Q = new Object[n];
	for(int i=1; i<=n; i++)
	{
		Q[i-1].ID = i;
		Q[i-1].d = 1.0 * p[i]/w[i];
		P += p[i];
		W += w[i];
	}

	if(W <= c)//装入所有物品
	{
		return P;
	}

	//依物品单位重量价值排序
	BubbleSort(Q,n);

	Knap<Typew,Typep> K;
	K.p = new Typep[n+1];
	K.w = new Typew[n+1];

	for(int i=1; i<=n; i++)
	{
		K.p[i] = p[Q[i-1].ID];
		K.w[i] = w[Q[i-1].ID];
	}

	K.cp = 0;
	K.cw = 0;
	K.c = c;
	K.n = n;
	K.bestp = 0;

	//回溯搜索
	K.Backtrack(1);

	delete []Q;
	delete []K.w;
	delete []K.p;
	return K.bestp;
}

template<class Type>
void BubbleSort(Type a[],int n)
{
	 //记录一次遍历中是否有元素的交换   
     bool exchange;  
	 for(int i=0; i<n-1;i++)  
	 {  
		exchange = false ;  
		for(int j=i+1; j<=n-1; j++)  
		{  
			if(a[j]<=a[j-1])  
			{  
				Swap(a[j],a[j-1]); 
				exchange = true;  
			}   
		}   
		//如果这次遍历没有元素的交换,那么排序结束   
		if(false == exchange)  
		{
			 break ;  
		}
	 }
}

template <class Type>
inline void Swap(Type &a,Type &b)
{
	Type temp = a;
	a = b;
	b = temp;
}
     计算上界需要O(n)时间,在最坏情况下有O(2^n)个右儿子节点需要计算上界,故解0-1背包问题的回溯算法所需要的计算时间为O(n2^n)。程序运行结果如图:


你可能感兴趣的:(0029算法笔记——【回溯法】n后问题和0-1背包问题)