0029算法笔记回溯法n后问题和01背包问题.docx
《0029算法笔记回溯法n后问题和01背包问题.docx》由会员分享,可在线阅读,更多相关《0029算法笔记回溯法n后问题和01背包问题.docx(15页珍藏版)》请在冰豆网上搜索。
![0029算法笔记回溯法n后问题和01背包问题.docx](https://file1.bdocx.com/fileroot1/2022-11/23/a76a9517-7ea4-4696-b7d1-0e361800dd36/a76a9517-7ea4-4696-b7d1-0e361800dd361.gif)
0029算法笔记回溯法n后问题和01背包问题
1、n后问题
问题描述:
在n×n格的棋盘上放置彼此不受攻击的n个皇后。
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n后问题等价于在n×n格的棋盘上放置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。
问题解析:
用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。
因此有:
由此约束条件剪去不满足行、列和斜线约束的子树。
程序的递归回溯实现如下:
[cpp] viewplain copy
1.//n后问题 回溯法计算 递归
2.#include "stdafx.h"
3.#include
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<"<22. m=nQueen(n);
23. cout<24. cout<"<25. return 0;
26.}
27.
28.bool Queen:
:
Place(int k)
29.{
30. for (int j=1;j31. {
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<48. }
49. cout<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] viewplain copy
1.//n后问题 回溯法计算 迭代
2.#include "stdafx.h"
3.#include
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<"<22. m=nQueen(n);
23. cout<24. cout<"<25. return 0;
26.}
27.
28.bool Queen:
:
Place(int k)
29.{
30. for (int j=1;j31. {
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<59. }
60. cout<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.}
程序运行结果如图:
2、0-1背包问题
问题描述:
给定n种物品和一背包。
物品i的重量是wi,其价值为vi,背包的容量为C。
问:
应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
形式化描述:
给定c>0,wi>0,vi>0,1≤i≤n.要求找一n元向量(x1,x2,…,xn,),xi∈{0,1},∋∑wixi≤c,且∑vixi达最大.即一个特殊的整数规划问题。
问题解析:
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] viewplain copy
1.//0-1背包问题 回溯法求解
2.#include "stdafx.h"
3.#include
4.using namespace std;
5.
6.template
7.class Knap
8.{
9. template
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
27.Typep Knapsack(Typep p[],Typew w[],Typew c,int n);
28.
29.template
30.inline void Swap(Type &a,Type &b);
31.
32.template
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<<"背包容量为:
"<43. cout<<"物品重量和价值分别为:
"<44.
45. for(int i=1; i<=n; i++)
46. {
47. cout<<"("<48. }
49. cout<50.
51. cout<<"背包能装下的最大价值为:
"<52. return 0;
53.}
54.
55.template
56.void Knap:
:
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
80.Typep Knap:
:
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
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
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 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
166.void BubbleSort(Type a[],int n)
167.{
168. //记录一次遍历中是否有元素的交换
169. bool exchange;
170. for(int i=0; i171. {
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