智能算法变邻域搜索算法(Variable Neighborhood Search,VNS)超详细解析和TSP代码实例以及01背包代码实例
Posted infroad
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了智能算法变邻域搜索算法(Variable Neighborhood Search,VNS)超详细解析和TSP代码实例以及01背包代码实例相关的知识,希望对你有一定的参考价值。
喜欢的话可以扫码关注我们的公众号哦,更多精彩尽在微信公众号【程序猿声】
00 目录
- 局部搜索再次科普
- 变邻域搜索
- 造轮子写代码
01 局部搜索科普三连
虽然之前做的很多篇启发式的算法都有跟大家提过局部搜索这个概念,为了加深大家的印象,在变邻域主角登场之前还是给大家科普一下相关概念。热热身嘛~
1.1 局部搜索是什么玩意儿?
官方一点:局部搜索是解决最优化问题的一种启发式算法。对于某些计算起来非常复杂的最优化问题,比如各种NP完全问题,要找到最优解需要的时间随问题规模呈指数增长,因此诞生了各种启发式算法来退而求其次寻找次优解,是一种近似算法(Approximate algorithms),以时间换精度的思想。局部搜索就是其中的一种方法。
通俗一点:局部搜索算法是对一类算法的统称,符合其框架的算法很多,比如之前公众号推文中介绍的爬山法、模拟退火法和禁忌搜索算法都属于局部搜索算法。尽管各个算法在优化过程中的细节存在差异,但在优化流程上呈现出很大的共性。
它的基本原理是在临近解中迭代,使目标函数逐步优化,直至不能再优化为止。
1.2 局部搜索的过程
我们可以将局部搜索算法的统一框架描述为:
- 算法从一个或若干个初始解出发。
- 在算法参数控制下由当前状态的邻域中产生若干个候选解。
- 以某种策略在候选解中确定新的当前解。
- 伴随控制参数的调节,重复执行上述搜索过程,直至满足算法终止准则。
- 结束搜索过程并输出优化结果。
1.3 局部搜索的几大要素
局部搜索算法主要包含五大要素:
- 目标函数:用来判断解的优劣。
- 邻域的定义:根据不同问题,有着不同的邻域定义。
- 初始解的产生规则
- 新解的产生和接受规则
- 算法终止准则
其中前两个要素的定义和算法要解决的特定问题有关,而且不同的人对同一问题可能有完全不同的定义。后三个要素定义的不同则会产生各种不同的局部搜索算法,而它们的效率和最终解的质量也会有很大的差异。
02 变邻域搜索算法
2.1 什么是VNS?
对上面的局部搜索有一定的印象以后,理解变邻域搜索也不难了。其实说白了,变邻域搜索算法(VNS)就是一种改进型的局部搜索算法。它利用不同的动作构成的邻域结构进行交替搜索,在集中性和疏散性之间达到很好的平衡。其思想可以概括为“变则通”。
变邻域搜索依赖于以下事实:
- 一个邻域结构的局部最优解不一定是另一个邻域结构的局部最优解。
- 全局最优解是所有可能邻域的局部最优解。
它由主要由以下两个部分组成:
- variable neighborhood descent (VND)
- shaking procedure
大家别急,下面我们将会对这两个部分进行分析。然后,before that……
2.2 你们一定想知道邻域是什么?
官方一点:所谓邻域,简单的说即是给定点附近其他点的集合。在距离空间中,邻域一般被定义为以给定点为圆心的一个圆;而在组合优化问题中,邻域一般定义为由给定转化规则对给定的问题域上每结点进行转化所得到的问题域上结点的集合。
通俗一点:邻域就是指对当前解进行一个操作(这个操作可以称之为邻域动作)可以得到的所有解的集合。那么邻域的本质区别就在于邻域动作的不同了。
2.3 还是要说说邻域动作
邻域动作是一个函数,通过这个函数,对当前解s,产生其相应的邻居解集合。例如:对于一个bool型问题,其当前解为:s = 1001,当将邻域动作定义为翻转其中一个bit时,得到的邻居解的集合N(s)={0001,1101,1011,1000},其中N(s) ∈ S。同理,当将邻域动作定义为互换相邻bit时,得到的邻居解的集合N(s)={0101,1001,1010}。
2.4 variable neighborhood descent (VND)
VND其实就是一个算法框架,它的过程描述如下:
- 初始解S,定义M个邻域,记为Nk(k = 1, 2, 3……m),i = 1。
- 使用邻域结构Ni进行搜索,直到陷入局部最优解S′ 。
- 如果S′ 优于S,令S=S′,i=1; 否则,i++。
- 如果i≤m ,转步骤2。
- 输出最优解S。
我知道没图你们是不会点进来的……
VND的图解如下:
只说两点,再问自杀:
- 当在本邻域搜索找不出一个比当前解更优的解的时候,我们就跳到下一个邻域继续进行搜索。如图中虚黑线所示。
- 当在本邻域搜索找到了一个比当前解更优的解的时候,我们就跳回第一个邻域重新开始搜索。如图中红线所示。
之前我们把局部搜索比喻作爬山的过程,那么每变换一次邻域,也可以理解为切换了搜索的地形(landscape)。效果如下 :
每一次跳跃,得到都是一个新的世界……
伪代码描述如下:
2.5 shaking procedure
其实呀,这玩意儿。说白了就是一个扰动算子,类似于邻域动作的这么一个东西。通过这个算子,可以产生不同的邻居解。虽然名词很多看起来很高大上,扰动、抖动、邻域动作这几个本质上还是没有什么区别的。都是通过一定的规则,将一个解变换到另一个解而已。这里读者还是抓其本质,不要被表象所迷惑了就好。
2.6 VNS过程
在综合了前面这么多的知识以后,VNS的过程其实非常简单。可以描述为以下几步:
- 产生初始解s1。
- shaking s1,得到解s2。
- 对解s2进行VND,得到解s3。
- 如果达到边界条件,结束程序,输出最优解。否则跳回第二步。
结合伪代码,一目了然:
03 变邻域搜索解决TSP问题
本次代码还是基于求解TSP旅行商问题的。至于什么是TSP问题,小编这实在是不想科普啦……
代码是基于迭代搜索那个代码魔改过来的。其实看了这么多启发式算法解TSP问题的代码。想必各位都有了一个比较清晰的认识,其实呀。之前介绍的模拟退火、遗传算法、迭代搜索和现在的变邻域等等,是十分相似滴。最大的不同在于算法框架的不同而已,像什么扰动啦,邻域动作啦。代码基本是不变的。所以大家可以多联想,多思考,学习就是一个探求事物本质的过程嘛!
简要说说算法vnd里面两个邻域使用的算子:
-
two_opt_swap
没啥好说的,区间反转。直接上图: -
two_h_opt_swap
还是要说一点,随机产生两点,塞进新排列头部。其余的按顺序往后逐个塞进去。嗯,来看图片~
看代码吧。
1////////////////////////
2//TSP问题 变邻域搜索求解代码
3//基于Berlin52例子求解
4//作者:infinitor
5//时间:2018-04-12
6////////////////////////
7
8
9#include <iostream>
10#include <cmath>
11#include <stdlib.h>
12#include <time.h>
13#include <vector>
14#include <windows.h>
15#include <memory.h>
16#include <string.h>
17#include <iomanip>
18
19#define DEBUG
20
21using namespace std;
22
23#define CITY_SIZE 52 //城市数量
24
25
26//城市坐标
27typedef struct candidate
28{
29 int x;
30 int y;
31}city, CITIES;
32
33//解决方案
34typedef struct Solution
35{
36 int permutation[CITY_SIZE]; //城市排列
37 int cost; //该排列对应的总路线长度
38}SOLUTION;
39
40//城市排列
41int permutation[CITY_SIZE];
42//城市坐标数组
43CITIES cities[CITY_SIZE];
44
45
46//berlin52城市坐标,最优解7542好像
47CITIES berlin52[CITY_SIZE] =
48{
49{ 565,575 },{ 25,185 },{ 345,750 },{ 945,685 },{ 845,655 },
50{ 880,660 },{ 25,230 },{ 525,1000 },{ 580,1175 },{ 650,1130 },{ 1605,620 },
51{ 1220,580 },{ 1465,200 },{ 1530,5 },{ 845,680 },{ 725,370 },{ 145,665 },
52{ 415,635 },{ 510,875 },{ 560,365 },{ 300,465 },{ 520,585 },{ 480,415 },
53{ 835,625 },{ 975,580 },{ 1215,245 },{ 1320,315 },{ 1250,400 },{ 660,180 },
54{ 410,250 },{ 420,555 },{ 575,665 },{ 1150,1160 },{ 700,580 },{ 685,595 },
55{ 685,610 },{ 770,610 },{ 795,645 },{ 720,635 },{ 760,650 },{ 475,960 },
56{ 95,260 },{ 875,920 },{ 700,500 },{ 555,815 },{ 830,485 },{ 1170,65 },
57{ 830,610 },{ 605,625 },{ 595,360 },{ 1340,725 },{ 1740,245 }
58};
59//优化值
60int Delta1[CITY_SIZE][CITY_SIZE] = { 0 };
61
62//随机数产生函数,利用系统时间,精确到微妙,再加一个变量i组合产生随机数。
63//单单用srand(time(NULL)) + rand()达不到效果,因为函数执行太快。时间间隔太短
64int randEx(int i)
65{
66 LARGE_INTEGER seed;
67 QueryPerformanceFrequency(&seed);
68 QueryPerformanceCounter(&seed);
69 srand((unsigned int)seed.QuadPart + i);
70
71 return rand();
72}
73
74
75//计算两个城市间距离
76int distance_2city(city c1, city c2)
77{
78 int distance = 0;
79 distance = sqrt((double)((c1.x - c2.x)*(c1.x - c2.x) + (c1.y - c2.y)*(c1.y - c2.y)));
80
81 return distance;
82}
83
84//根据产生的城市序列,计算旅游总距离
85//所谓城市序列,就是城市先后访问的顺序,比如可以先访问ABC,也可以先访问BAC等等
86//访问顺序不同,那么总路线长度也是不同的
87//p_perm 城市序列参数
88int cost_total(int * cities_permutation, CITIES * cities)
89{
90 int total_distance = 0;
91 int c1, c2;
92 //逛一圈,看看最后的总距离是多少
93 for (int i = 0; i < CITY_SIZE; i++)
94 {
95 c1 = cities_permutation[i];
96 if (i == CITY_SIZE - 1) //最后一个城市和第一个城市计算距离
97 {
98 c2 = cities_permutation[0];
99 }
100 else
101 {
102 c2 = cities_permutation[i + 1];
103 }
104 total_distance += distance_2city(cities[c1], cities[c2]);
105 }
106
107 return total_distance;
108}
109
110//获取随机城市排列
111void random_permutation(int * cities_permutation)
112{
113 int i, r, temp;
114 for (i = 0; i < CITY_SIZE; i++)
115 {
116 cities_permutation[i] = i; //初始化城市排列,初始按顺序排
117 }
118
119
120 for (i = 0; i < CITY_SIZE; i++)
121 {
122 //城市排列顺序随机打乱
123 srand((unsigned int)time(NULL));
124 int j = rand();
125 r = randEx(++j) % (CITY_SIZE - i) + i;
126 temp = cities_permutation[i];
127 cities_permutation[i] = cities_permutation[r];
128 cities_permutation[r] = temp;
129 }
130}
131//对应two_opt_swap的去重
132int calc_delta1(int i, int k, int *tmp, CITIES * cities) {
133 int delta = 0;
134 /*
135 以下计算说明:
136 对于每个方案,翻转以后没必要再次重新计算总距离
137 只需要在翻转的头尾做个小小处理
138
139 比如:
140 有城市序列 1-2-3-4-5 总距离 = d12 + d23 + d34 + d45 + d51 = A
141 翻转后的序列 1-4-3-2-5 总距离 = d14 + d43 + d32 + d25 + d51 = B
142 由于 dij 与 dji是一样的,所以B也可以表示成 B = A - d12 - d45 + d14 + d25
143 下面的优化就是基于这种原理
144 */
145 if (i == 0)
146 {
147 if (k == CITY_SIZE - 1)
148 {
149 delta = 0;
150 }
151 else
152 {
153 delta = 0
154 - distance_2city(cities[tmp[k]], cities[tmp[k + 1]])
155 + distance_2city(cities[tmp[i]], cities[tmp[k + 1]])
156 - distance_2city(cities[tmp[CITY_SIZE - 1]], cities[tmp[i]])
157 + distance_2city(cities[tmp[CITY_SIZE - 1]], cities[tmp[k]]);
158 }
159
160 }
161 else
162 {
163 if (k == CITY_SIZE - 1)
164 {
165 delta = 0
166 - distance_2city(cities[tmp[i - 1]], cities[tmp[i]])
167 + distance_2city(cities[tmp[i - 1]], cities[tmp[k]])
168 - distance_2city(cities[tmp[0]], cities[tmp[k]])
169 + distance_2city(cities[tmp[i]], cities[tmp[0]]);
170 }
171 else
172 {
173 delta = 0
174 - distance_2city(cities[tmp[i - 1]], cities[tmp[i]])
175 + distance_2city(cities[tmp[i - 1]], cities[tmp[k]])
176 - distance_2city(cities[tmp[k]], cities[tmp[k + 1]])
177 + distance_2city(cities[tmp[i]], cities[tmp[k + 1]]);
178 }
179 }
180
181 return delta;
182}
183
184
185/*
186去重处理,对于Delta数组来说,对于城市序列1-2-3-4-5-6-7-8-9-10,如果对3-5应用了邻域操作2-opt , 事实上对于
1877-10之间的翻转是不需要重复计算的。 所以用Delta提前预处理一下。
188
189当然由于这里的计算本身是O(1) 的,事实上并没有带来时间复杂度的减少(更新操作反而增加了复杂度)
190如果delta计算 是O(n)的,这种去重操作效果是明显的。
191*/
192//对应two_opt_swap的去重更新
193void Update1(int i, int k, int *tmp, CITIES * cities, int Delta[CITY_SIZE][CITY_SIZE]) {
194 if (i && k != CITY_SIZE - 1) {
195 i--; k++;
196 for (int j = i; j <= k; j++) {
197 for (int l = j + 1; l < CITY_SIZE; l++) {
198 Delta[j][l] = calc_delta1(j, l, tmp, cities);
199 }
200 }
201
202 for (int j = 0; j < k; j++) {
203 for (int l = i; l <= k; l++) {
204 if (j >= l) continue;
205 Delta[j][l] = calc_delta1(j, l, tmp, cities);
206 }
207 }
208 }// 如果不是边界,更新(i-1, k + 1)之间的
209 else {
210 for (i = 0; i < CITY_SIZE - 1; i++)
211 {
212 for (k = i + 1; k < CITY_SIZE; k++)
213 {
214 Delta[i][k] = calc_delta1(i, k, tmp, cities);
215 }
216 }
217 }// 边界要特殊更新
218
219}
220
221
222// two_opt_swap算子
223void two_opt_swap(int *cities_permutation, int b, int c)
224{
225 vector<int> v;
226 for (int i = 0; i < b; i++)
227 {
228 v.push_back(cities_permutation[i]);
229 }
230 for (int i = c; i >= b; i--)
231 {
232 v.push_back(cities_permutation[i]);
233 }
234 for (int i = c + 1; i < CITY_SIZE; i++)
235 {
236 v.push_back(cities_permutation[i]);
237 }
238
239 for (int i = 0; i < CITY_SIZE; i++)
240 {
241 cities_permutation[i] = v[i];
242 }
243
244}
245
246//邻域结构1 使用two_opt_swap算子
247void neighborhood_one(SOLUTION & solution, CITIES *cities)
248{
249 int i, k, count = 0;
250 int max_no_improve = 30;
251
252 int inital_cost = solution.cost; //初始花费
253 int now_cost = 0;
254
255 SOLUTION current_solution = solution;
256
257 for (i = 0; i < CITY_SIZE - 1; i++)
258 {
259 for (k = i + 1; k < CITY_SIZE; k++)
260 {
261 Delta1[i][k] = calc_delta1(i, k, solution.permutation, cities);
262 }
263 }
264
265 do
266 {
267 count++;
268 for (i = 0; i < CITY_SIZE - 1; i++)
269 {
270 for (k = i + 1; k < CITY_SIZE; k++)
271 {
272 if (Delta1[i][k] < 0)
273 {
274 current_solution = solution;
275 two_opt_swap(current_solution.permutation, i, k);
276
277 now_cost = inital_cost + Delta1[i][k];
278 current_solution.cost = now_cost;
279
280 solution = current_solution;
281 inital_cost = solution.cost;
282
283 Update1(i, k, solution.permutation, cities, Delta1);
284
285 count = 0; //count复位
286
287 }
288
289 }
290 }
291 }while (count <= max_no_improve);
292
293}
294
295//two_h_opt_swap的去重
296int calc_delta2(int i, int k, int *cities_permutation, CITIES * cities)
297{
298 int delta = 0;
299 if (i == 0)
300 {
301 if ( k == 1)
302 {
303 delta = 0;
304 }
305 else if ( k == CITY_SIZE -1)
306 {
307 delta = 0
308 - distance_2city(cities[cities_permutation[i]], cities[cities_permutation[i + 1]])
309 - distance_2city(cities[cities_permutation[k]], cities[cities_permutation[k - 1]])
310 + distance_2city(cities[cities_permutation[k]], cities[cities_permutation[2]])
311 + distance_2city(cities[cities_permutation[k - 1]], cities[cities_permutation[i]]);
312 }
313 else
314 {
315 delta = 0
316 - distance_2city(cities[cities_permutation[i]], cities[cities_permutation[i + 1]])
317 - distance_2city(cities[cities_permutation[k]], cities[cities_permutation[k - 1]])
318 - distance_2city(cities[cities_permutation[k]], cities[cities_permutation[k + 1]])
319 + distance_2city(cities[cities_permutation[k - 1]], cities[cities_permutation[k + 1]])
320 + distance_2city(cities[cities_permutation[i]], cities[cities_permutation[k]])
321 + distance_2city(cities[cities_permutation[k]], cities[cities_permutation[i + 1]]);
322 }
323 }
324 else
325 {
326 if ( k == CITY_SIZE - 1)
327 {
328 delta = 0
329 - distance_2city(cities[cities_permutation[i]], cities[cities_permutation[i + 1]])
330 - distance_2city(cities[cities_permutation[0]], cities[cities_permutation[k]])
331 + distance_2city(cities[cities_permutation[k]], cities[cities_permutation[i + 1]])
332 + distance_2city(cities[cities_permutation[i]], cities[cities_permutation[k]]);
333 }
334 else if (k == i + 1)
335 {
336 delta = 0;
337 }
338 else
339 {
340 delta = 0
341 - distance_2city(cities[cities_permutation[i]], cities[cities_permutation[i + 1]])
342 - distance_2city(cities[cities_permutation[k]], cities[cities_permutation[k + 1]])
343 - distance_2city(cities[cities_permutation[k]], cities[cities_permutation[k - 1]])
344 + distance_2city(cities[cities_permutation[i]], cities[cities_permutation[k]])
345 + distance_2city(cities[cities_permutation[k]], cities[cities_permutation[i + 1]])
346 + distance_2city(cities[cities_permutation[k - 1]], cities[cities_permutation[k + 1]]);
347
348 }
349 }
350
351 return delta;
352
353}
354
355
356
357//two_h_opt_swap算子
358void two_h_opt_swap(int *cities_permutation, int a, int d)
359{
360 int n = CITY_SIZE;
361 vector<int> v;
362 v.push_back(cities_permutation[a]);
363 v.push_back(cities_permutation[d]);
364 // i = 1 to account for a already added
365 for (int i = 1; i < n; i++)
366 {
367 int idx = (a + i) % n;
368 // Ignore d which has been added already
369 if (idx != d)
370 {
371 v.push_back(cities_permutation[idx]);
372 }
373 }
374
375 for (int i = 0; i < v.size(); i++)
376 {
377 cities_permutation[i] = v[i];
378 }
379
380}
381
382
383//邻域结构2 使用two_h_opt_swap算子
384void neighborhood_two(SOLUTION & solution, CITIES *cities)
385{
386 int i, k, count = 0;
387 int max_no_improve = 30;
388 SOLUTION current_solution = solution;
389
390 int inital_cost = solution.cost; //初始花费
391 int now_cost = 0;
392 int delta = 0;
393
394 do
395 {
396 count++;
397 for (i = 0; i < CITY_SIZE - 1; i++)
398 {
399 for (k = i + 1; k < CITY_SIZE; k++)
400 {
401
402 delta = calc_delta2(i, k, current_solution.permutation, cities);
403
404 if (delta <= 0)
405 {
406 current_solution = solution;
407 two_h_opt_swap(current_solution.permutation, i, k);
408
409 now_cost = inital_cost + delta;
410 current_solution.cost = now_cost;
411
412
413 solution = current_solution;
414
415 inital_cost = solution.cost;
416
417 count = 0; //count复位
418 }
419 }
420 }
421 } while (count <= max_no_improve);
422}
423
424
425//VND
426//best_solution最优解
427//current_solution当前解
428void variable_neighborhood_descent(SOLUTION & solution, CITIES * cities)
429{
430
431 SOLUTION current_solution = solution;
432 int l = 1;
433 cout <<"=====================VariableNeighborhoodDescent=====================" << endl;
434 while(true)
435 {
436 switch (l)
437 {
438 case 1:
439 neighborhood_one(current_solution, cities);
440 cout << setw(45) << setiosflags(ios::left) <<"Now in neighborhood_one , current_solution = " << current_solution.cost << setw(10) << setiosflags(ios::left) << " solution = " << solution.cost << endl;
441 if (current_solution.cost < solution.cost)
442 {
443 solution = current_solution;
444 l = 0;
445 }
446 break;
447 case 2:
448 neighborhood_two(current_solution, cities);
449 cout << setw(45) << setiosflags(ios::left) << "Now in neighborhood_two , current_solution = " << current_solution.cost << setw(10) << setiosflags(ios::left) << " solution = " << solution.cost << endl;
450 if (current_solution.cost < solution.cost)
451 {
452 solution = current_solution;
453 l = 0;
454 }
455 break;
456
457 default:
458 return;
459 }
460 l++;
461
462 }
463
464}
465
466//将城市序列分成4块,然后按块重新打乱顺序。
467//用于扰动函数
468void double_bridge_move(int * cities_permutation)
469{
470 srand((unsigned int)time(NULL));
471 int j = rand();
472 int pos1 = 1 + randEx(++j) % (CITY_SIZE / 4);
473 int pos2 = pos1 + 1 + randEx(++j) % (CITY_SIZE / 4);
474 int pos3 = pos2 + 1 + randEx(++j) % (CITY_SIZE / 4);
475
476 int i;
477 vector<int> v;
478 //第一块
479 for (i = 0; i < pos1; i++)
480 {
481 v.push_back(cities_permutation[i]);
482 }
483
484 //第二块
485 for (i = pos3; i < CITY_SIZE; i++)
486 {
487 v.push_back(cities_permutation[i]);
488 }
489 //第三块
490 for (i = pos2; i < pos3; i++)
491 {
492 v.push_back(cities_permutation[i]);
493 }
494
495 //第四块
496 for (i = pos1; i < pos2; i++)
497 {
498 v.push_back(cities_permutation[i]);
499 }
500
501
502 for (i = 0; i < (int)v.size(); i++)
503 {
504 cities_permutation[i] = v[i];
505 }
506
507
508}
509
510//抖动
511void shaking(SOLUTION &solution, CITIES *cities)
512{
513 double_bridge_move(solution.permutation);
514 solution.cost = cost_total(solution.permutation, cities);
515}
516
517
518void variable_neighborhood_search(SOLUTION & best_solution, CITIES * cities)
519{
520
521 int max_iterations = 5;
522
523 int count = 0, it = 0;
524
525 SOLUTION current_solution = best_solution;
526
527 //算法开始
528 do
529 {
530 cout << endl << " Algorithm VNS iterated " << it+1 << " times" << endl;
531 count++;
532 it++;
533 shaking(current_solution, cities);
534
535 variable_neighborhood_descent(current_solution, cities);
536
537 if (current_solution.cost < best_solution.cost)
538 {
539 best_solution = current_solution;
540 count = 0;
541 }
542
543 cout << " 全局best_solution = " << best_solution.cost << endl;
544
545 } while (count <= max_iterations);
546
547
548}
549
550
551int main()
552{
553
554 SOLUTION best_solution;
555
556 random_permutation(best_solution.permutation);
557 best_solution.cost = cost_total(best_solution.permutation, berlin52);
558
559 cout << "初始总路线长度 = " << best_solution.cost << endl;
560
561 variable_neighborhood_search(best_solution, berlin52);
562
563 cout << endl << endl << "搜索完成! 最优路线总长度 = " << best_solution.cost << endl;
564 cout << "最优访问城市序列如下:" << endl;
565 for (int i = 0; i < CITY_SIZE; i++)
566 {
567 cout << setw(4) << setiosflags(ios::left) << best_solution.permutation[i];
568 }
569
570 cout << endl << endl;
571
572 return 0;
573}
程序结果:
04 变邻域搜索解决01背包问题
//TO DO
1#include <iostream>
2#include <vector>
3#include <ctime>
4#include <iomanip>
5using namespace std;
6
7// 物品的数量 每一个物品有0和1两种选择 0代表选择当前物品 1代表不选择当前物品
8const int n = 100;
9
10//算法最大迭代次数
11const int Max_Iteration = 1000;
12
13//邻域数量
14const int MaxFlip = 3;
15int flip = 1;
16
17
18//背包最大容量
19const int maxWeight = 5 * n;
20
21//记录已经检查的背包数量
22int solutionsChecked = 0;
23
24//物品对应价值&&重量
25int values[n] = { 0 };
26int weights[n] = { 0 };
27
28//随机数种子
29const int seed = 5113; //2971
30
31/************************************************************************/
32/*
33 解决方案类:
34
35*/
36/************************************************************************/
37
38typedef struct Knapsack_Problem_Solution
39{
40 int selection[n] = { 0 }; //当前方案的物品选择情况 selection[i] == 0 or 1 <==> 不选择 or 选择 第i个物品
41 int total_values = 0; //当前方案下物品总价值
42 int total_weights = 0; //当前方案下物品总重量
43}KP_Solution;
44
45//对selection[n]进行评价,计算total_values和total_weights
46void Evaluate_Solution(KP_Solution & x)
47{
48 x.total_values = 0;
49 x.total_weights = 0;
50 for (int i = 0; i < n; i++)
51 {
52 x.total_values += x.selection[i] * values[i];
53 x.total_weights += x.selection[i] * weights[i];
54 }
55
56 if (x.total_weights > maxWeight)
57 {
58 x.total_values = maxWeight - x.total_weights; //超过背包最大容纳重量,价值设置为负数
59 }
60
61}
62
63
64//邻居解集合
65vector<KP_Solution> nbrhood;
66
67void MySwap(int &a, int &b)
68{
69 int temp = a;
70 a = b;
71 b = temp;
72}
73
74//利用邻域动作生成邻居解
75void neighborhood(KP_Solution &x, int flip)
76{
77 //邻域动作1
78 if (flip == 1)
79 {
80 nbrhood.clear();
81 for (int i = 0; i < n; i++)
82 {
83 nbrhood.push_back(x);
84 if (nbrhood[i].selection[i] == 1)
85 {
86 nbrhood[i].selection[i] = 0;
87 }
88 else
89 {
90 nbrhood[i].selection[i] = 1;
91 }
92 }
93 }
94 //邻域动作2
95 else if (flip == 2)
96 {
97 nbrhood.clear();
98 int a = -1;
99 for (int i = 0; i < n; i++)
100 {
101 for (int j = i; j < n; j++)
102 {
103 if (i != j)
104 {
105 a += 1;
106 nbrhood.push_back(x);
107
108 if (nbrhood[a].selection[i] == 1)
109 {
110 nbrhood[a].selection[i] = 0;
111 }
112 else
113 {
114 nbrhood[a].selection[i] = 1;
115 }
116
117 if (nbrhood[a].selection[j] == 1)
118 {
119 nbrhood[a].selection[j] = 0;
120 }
121 else
122 {
123 nbrhood[a].selection[j] = 1;
124 }
125
126 }
127 }
128 }
129 }
130 //邻域动作3
131 else
132 {
133 nbrhood.clear();
134 for (int i = 0; i < n; i++)
135 {
136 nbrhood.push_back(x);
137 if ( i < 3)
138 {
139 MySwap(nbrhood[i].selection[i], nbrhood[i].selection[n + i - 3]);
140 }
141 else
142 {
143 MySwap(nbrhood[i].selection[i], nbrhood[i].selection[i - 3]);
144 }
145 }
146 }
147
148
149}
150//随机生成价值和重量
151void Rand_Value_Weight()
152{
153 srand(seed);
154 for (int i = 0; i < n; i++)
155 {
156 values[i] = rand() % 90 + 10; // 10 - 100
157 weights[i] = rand() % 15 + 5; // 5 - 20
158 }
159}
160
161//随机生成解决方案
162void Random_Solution(KP_Solution &x)
163{
164 x.total_values = 0;
165 x.total_weights = 0;
166 srand((unsigned int)time(NULL));
167 for (int i = 0; i < n; i++)
168 {
169 double rate = (rand() % 100) / 100.0;
170 if ( rate < 0.8 )
171 {
172 x.selection[i] = 0;
173 }
174 else
175 {
176 x.selection[i] = 1;
177 }
178 }
179}
180
181void Variable_Neighborhood_Descent(KP_Solution &x)
182{
183 int flip = 1;
184 KP_Solution x_curr;
185 while ( flip < MaxFlip + 1)
186 {
187 neighborhood(x, flip);
188 x_curr = nbrhood[0];
189 Evaluate_Solution(x_curr);
190
191 for(unsigned int i = 1; i < nbrhood.size(); i++)
192 {
193 solutionsChecked += 1;
194
195 Evaluate_Solution(nbrhood[i]);
196
197 if (nbrhood[i].total_values > x_curr.total_values)
198 {
199 x_curr = nbrhood[i];
200 }
201 }
202 //邻域复位
203 if (x_curr.total_weights > x.total_weights)
204 {
205 x = x_curr;
206 flip = 1;
207 }
208 else
209 {
210 flip += 1;
211 }
212 }
213}
214
215
216
217
218void Shaking_Procdure(KP_Solution &x)
219{
220 srand((unsigned int)time(NULL));
221
222 int num = rand() % (n / 10) + 3; // 3 - 8
223 for (int i = 0; i < num; i++)
224 {
225 int pos = rand() % n;
226 if (x.selection[i] == 0)
227 {
228 x.selection[i] = 1;
229 }
230 else
231 {
232 x.selection[i] = 0;
233 }
234 }
235
236 Evaluate_Solution(x);
237}
238
239void Variable_Neighborhood_Search(KP_Solution &x, int iteration)
240{
241 KP_Solution best = x;
242 Variable_Neighborhood_Descent(best);
243 for (int i = 0; i < iteration; i++)
244 {
245 Shaking_Procdure(x);
246
247 Variable_Neighborhood_Descent(x);
248 if (best.total_values < x.total_values)
249 {
250 best = x;
251 }
252 }
253
254 x = best;
255}
256
257int main()
258{
259 KP_Solution kpx;
260
261 Rand_Value_Weight();
262
263 Random_Solution(kpx);
264
265 Variable_Neighborhood_Search(kpx, Max_Iteration);
266
267 cout << "石头重量为:" << endl;
268
269 for (int i = 0; i < n; i++)
270 {
271 cout << setw(2) <<weights[i] << " ";
272 if ((i + 1) % 25 == 0)
273 {
274 cout << endl;
275 }
276 }
277
278 cout << "
石头价值为:" << endl;
279
280 for (int i = 0; i < n; i++)
281 {
282 cout << values[i] << " ";
283 if ((i + 1) % 25 == 0)
284 {
285 cout << endl;
286 }
287 }
288
289 cout << endl << "最终结果: 已检查的总方案数 = " << solutionsChecked << endl;
290 cout << "背包最大容量为:" << maxWeight << endl;
291 cout << "找到最大价值为: " << kpx.total_values << endl;
292 cout << "背包当前重量为: " << kpx.total_weights << endl;
293
294 for (int i = 0; i < n; i++)
295 {
296 cout << kpx.selection[i] << " ";
297 if ((i+1) % 25 == 0)
298 {
299 cout << endl;
300 }
301 }
302
303 return 0;
304}
程序结果:
以上是关于智能算法变邻域搜索算法(Variable Neighborhood Search,VNS)超详细解析和TSP代码实例以及01背包代码实例的主要内容,如果未能解决你的问题,请参考以下文章
依据象限搜索及混合预计耗费的A*改进算法,包含8邻域及24邻域的改进
依据象限搜索及混合预计耗费的A*改进算法,包含8邻域及24邻域的改进
对于任何局部搜索算法,可以在多项式时间内完成在邻域中搜索的一步吗?