【智能算法】变邻域搜索算法(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 #include10 #include 11 #include 12 #include 13 #include 14 #include 15 #include 16 #include <string.h> 17 #include 18 #include 19 #define DEBUG 20 21 using namespace std; 22 23 #define CITY_SIZE 52 //城市数量 24 25 26 //城市坐标 27 typedef struct candidate 28 { 29 int x; 30 int y; 31 }city, CITIES; 32 33 //解决方案 34 typedef struct Solution 35 { 36 int permutation[CITY_SIZE]; //城市排列 37 int cost; //该排列对应的总路线长度 38 }SOLUTION; 39 40 //城市排列 41 int permutation[CITY_SIZE]; 42 //城市坐标数组 43 CITIES cities[CITY_SIZE]; 44 45 46 //berlin52城市坐标,最优解7542好像 47 CITIES 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 //优化值 60 int Delta1[CITY_SIZE][CITY_SIZE] = { 0 }; 61 62 63 //计算两个城市间距离 64 int distance_2city(city c1, city c2) 65 { 66 int distance = 0; 67 distance = sqrt((double)((c1.x - c2.x)*(c1.x - c2.x) + (c1.y - c2.y)*(c1.y - c2.y))); 68 69 return distance; 70 } 71 72 //根据产生的城市序列,计算旅游总距离 73 //所谓城市序列,就是城市先后访问的顺序,比如可以先访问ABC,也可以先访问BAC等等 74 //访问顺序不同,那么总路线长度也是不同的 75 //p_perm 城市序列参数 76 int cost_total(int * cities_permutation, CITIES * cities) 77 { 78 int total_distance = 0; 79 int c1, c2; 80 //逛一圈,看看最后的总距离是多少 81 for (int i = 0; i < CITY_SIZE; i++) 82 { 83 c1 = cities_permutation[i]; 84 if (i == CITY_SIZE - 1) //最后一个城市和第一个城市计算距离 85 { 86 c2 = cities_permutation[0]; 87 } 88 else 89 { 90 c2 = cities_permutation[i + 1]; 91 } 92 total_distance += distance_2city(cities[c1], cities[c2]); 93 } 94 95 return total_distance; 96 } 97 98 //获取随机城市排列 99 void random_permutation(int * cities_permutation) 100 { 101 int i, r, temp; 102 for (i = 0; i < CITY_SIZE; i++) 103 { 104 cities_permutation[i] = i; //初始化城市排列,初始按顺序排 105 } 106 107 random_shuffle(cities_permutation, cities_permutation + CITY_SIZE); //随机化排序 108 109 } 110 //对应two_opt_swap的去重 111 int calc_delta1(int i, int k, int *tmp, CITIES * cities) { 112 int delta = 0; 113 /* 114 以下计算说明: 115 对于每个方案,翻转以后没必要再次重新计算总距离 116 只需要在翻转的头尾做个小小处理 117 118 比如: 119 有城市序列 1-2-3-4-5 总距离 = d12 + d23 + d34 + d45 + d51 = A 120 翻转后的序列 1-4-3-2-5 总距离 = d14 + d43 + d32 + d25 + d51 = B 121 由于 dij 与 dji是一样的,所以B也可以表示成 B = A - d12 - d45 + d14 + d25 122 下面的优化就是基于这种原理 123 */ 124 if (i == 0) 125 { 126 if (k == CITY_SIZE - 1) 127 { 128 delta = 0; 129 } 130 else 131 { 132 delta = 0 133 - distance_2city(cities[tmp[k]], cities[tmp[k + 1]]) 134 + distance_2city(cities[tmp[i]], cities[tmp[k + 1]]) 135 - distance_2city(cities[tmp[CITY_SIZE - 1]], cities[tmp[i]]) 136 + distance_2city(cities[tmp[CITY_SIZE - 1]], cities[tmp[k]]); 137 } 138 139 } 140 else 141 { 142 if (k == CITY_SIZE - 1) 143 { 144 delta = 0 145 - distance_2city(cities[tmp[i - 1]], cities[tmp[i]]) 146 + distance_2city(cities[tmp[i - 1]], cities[tmp[k]]) 147 - distance_2city(cities[tmp[0]], cities[tmp[k]]) 148 + distance_2city(cities[tmp[i]], cities[tmp[0]]); 149 } 150 else 151 { 152 delta = 0 153 - distance_2city(cities[tmp[i - 1]], cities[tmp[i]]) 154 + distance_2city(cities[tmp[i - 1]], cities[tmp[k]]) 155 - distance_2city(cities[tmp[k]], cities[tmp[k + 1]]) 156 + distance_2city(cities[tmp[i]], cities[tmp[k + 1]]); 157 } 158 } 159 160 return delta; 161 } 162 163 164 /* 165 去重处理,对于Delta数组来说,对于城市序列1-2-3-4-5-6-7-8-9-10,如果对3-5应用了邻域操作2-opt , 事实上对于 166 7-10之间的翻转是不需要重复计算的。 所以用Delta提前预处理一下。 167 168 当然由于这里的计算本身是O(1) 的,事实上并没有带来时间复杂度的减少(更新操作反而增加了复杂度) 169 如果delta计算 是O(n)的,这种去重操作效果是明显的。 170 */ 171 //对应two_opt_swap的去重更新 172 void Update1(int i, int k, int *tmp, CITIES * cities, int Delta[CITY_SIZE][CITY_SIZE]) { 173 if (i && k != CITY_SIZE - 1) { 174 i--; k++; 175 for (int j = i; j <= k; j++) { 176 for (int l = j + 1; l < CITY_SIZE; l++) { 177 Delta[j][l] = calc_delta1(j, l, tmp, cities); 178 } 179 } 180 181 for (int j = 0; j < k; j++) { 182 for (int l = i; l <= k; l++) { 183 if (j >= l) continue; 184 Delta[j][l] = calc_delta1(j, l, tmp, cities); 185 } 186 } 187 }// 如果不是边界,更新(i-1, k + 1)之间的 188 else { 189 for (i = 0; i < CITY_SIZE - 1; i++) 190 { 191 for (k = i + 1; k < CITY_SIZE; k++) 192 { 193 Delta[i][k] = calc_delta1(i, k, tmp, cities); 194 } 195 } 196 }// 边界要特殊更新 197 198 } 199 200 201 // two_opt_swap算子 202 void two_opt_swap(int *cities_permutation, int b, int c) 203 { 204 vector<int> v; 205 for (int i = 0; i < b; i++) 206 { 207 v.push_back(cities_permutation[i]); 208 } 209 for (int i = c; i >= b; i--) 210 { 211 v.push_back(cities_permutation[i]); 212 } 213 for (int i = c + 1; i < CITY_SIZE; i++) 214 { 215 v.push_back(cities_permutation[i]); 216 } 217 218 for (int i = 0; i < CITY_SIZE; i++) 219 { 220 cities_permutation[i] = v[i]; 221 } 222 223 } 224 225 //邻域结构1 使用two_opt_swap算子 226 void neighborhood_one(SOLUTION & solution, CITIES *cities) 227 { 228 int i, k, count = 0; 229 int max_no_improve = 60; 230 231 int inital_cost = solution.cost; //初始花费 232 int now_cost = 0; 233 234 //SOLUTION current_solution = solution; 235 236 for (int i = 0; i < CITY_SIZE - 1; i++) 237 { 238 for (k = i + 1; k < CITY_SIZE; k++) 239 { 240 Delta1[i][k] = calc_delta1(i, k, solution.permutation, cities); 241 } 242 } 243 244 do 245 { 246 count++; 247 for (i = 0; i < CITY_SIZE - 1; i++) 248 { 249 for (k = i + 1; k < CITY_SIZE; k++) 250 { 251 if (Delta1[i][k] < 0) 252 { 253 //current_solution = solution; 254 two_opt_swap(solution.permutation, i, k); 255 256 now_cost = inital_cost + Delta1[i][k]; 257 solution.cost = now_cost; 258 259 inital_cost = solution.cost; 260 261 Update1(i, k, solution.permutation, cities, Delta1); 262 263 count = 0; //count复位 264 265 } 266 267 } 268 } 269 }while (count <= max_no_improve); 270 271 } 272 273 //two_h_opt_swap的去重 274 int calc_delta2(int i, int k, int *cities_permutation, CITIES * cities) 275 { 276 int delta = 0; 277 if (i == 0) 278 { 279 if ( k == i+1) 280 { 281 delta = 0; 282 } 283 else if ( k == CITY_SIZE -1) 284 { 285 delta = 0 286 - distance_2city(cities[cities_permutation[i]], cities[cities_permutation[i + 1]]) 287 - distance_2city(cities[cities_permutation[k]], cities[cities_permutation[k - 1]]) 288 + distance_2city(cities[cities_permutation[k]], cities[cities_permutation[i+1]]) 289 + distance_2city(cities[cities_permutation[k - 1]], cities[cities_permutation[i]]); 290 } 291 else 292 { 293 delta = 0 294 - distance_2city(cities[cities_permutation[i]], cities[cities_permutation[i + 1]]) 295 - distance_2city(cities[cities_permutation[k]], cities[cities_permutation[k - 1]]) 296 - distance_2city(cities[cities_permutation[k]], cities[cities_permutation[k + 1]]) 297 + distance_2city(cities[cities_permutation[k - 1]], cities[cities_permutation[k + 1]]) 298 + distance_2city(cities[cities_permutation[i]], cities[cities_permutation[k]]) 299 + distance_2city(cities[cities_permutation[k]], cities[cities_permutation[i + 1]]); 300 } 301 } 302 else 303 { 304 if (k == i + 1) 305 { 306 delta = 0; 307 } 308 else if ( k == CITY_SIZE - 1) 309 { 310 delta = 0 311 - distance_2city(cities[cities_permutation[i]], cities[cities_permutation[i + 1]]) 312 - distance_2city(cities[cities_permutation[0]], cities[cities_permutation[k]]) 313 - distance_2city(cities[cities_permutation[k]], cities[cities_permutation[k-1]]) 314 + distance_2city(cities[cities_permutation[k]], cities[cities_permutation[i + 1]]) 315 + distance_2city(cities[cities_permutation[k-1]], cities[cities_permutation[0]]) 316 + distance_2city(cities[cities_permutation[i]], cities[cities_permutation[k]]); 317 } 318 else 319 { 320 delta = 0 321 - distance_2city(cities[cities_permutation[i]], cities[cities_permutation[i + 1]]) 322 - distance_2city(cities[cities_permutation[k]], cities[cities_permutation[k + 1]]) 323 - distance_2city(cities[cities_permutation[k]], cities[cities_permutation[k - 1]]) 324 + distance_2city(cities[cities_permutation[i]], cities[cities_permutation[k]]) 325 + distance_2city(cities[cities_permutation[k]], cities[cities_permutation[i + 1]]) 326 + distance_2city(cities[cities_permutation[k - 1]], cities[cities_permutation[k + 1]]); 327 328 } 329 } 330 331 return delta; 332 333 } 334 335 336 337 //two_h_opt_swap算子 338 void two_h_opt_swap(int *cities_permutation, int a, int d) 339 { 340 int n = CITY_SIZE; 341 vector<int> v; 342 v.push_back(cities_permutation[a]); 343 v.push_back(cities_permutation[d]); 344 // i = 1 to account for a already added 345 for (int i = 1; i < n; i++) 346 { 347 int idx = (a + i) % n; 348 // Ignore d which has been added already 349 if (idx != d) 350 { 351 v.push_back(cities_permutation[idx]); 352 } 353 } 354 355 for (int i = 0; i < v.size(); i++) 356 { 357 cities_permutation[i] = v[i]; 358 } 359 360 } 361 362 363 //邻域结构2 使用two_h_opt_swap算子 364 void neighborhood_two(SOLUTION & solution, CITIES *cities) 365 { 366 int i, k, count = 0; 367 int max_no_improve = 60; 368 int inital_cost = solution.cost; //初始花费 369 int now_cost = 0; 370 int delta = 0; 371 372 do 373 { 374 count++; 375 for (i = 0; i < CITY_SIZE - 1; i++) 376 { 377 for (k = i + 1; k < CITY_SIZE; k++) 378 { 379 380 delta = calc_delta2(i, k, solution.permutation, cities); 381 382 if (delta < 0) 383 { 384 //cout<<"delta = " < 385 386 two_h_opt_swap(solution.permutation, i, k); 387 388 now_cost = inital_cost + delta; 389 solution.cost = now_cost; 390 391 inital_cost = solution.cost; 392 393 count = 0; //count复位 394 } 395 } 396 } 397 } while (count <= max_no_improve); 398 } 399 400 401 //VND 402 //best_solution最优解 403 //current_solution当前解 404 void variable_neighborhood_descent(SOLUTION & solution, CITIES * cities) 405 { 406 407 SOLUTION current_solution = solution; 408 int l = 1; 409 cout <<"=====================VariableNeighborhoodDescent=====================" << endl; 410 while(true) 411 { 412 switch (l) 413 { 414 case 1: 415 neighborhood_one(current_solution, cities); 416 cout << setw(45) << setiosflags(ios::left) <<"Now in neighborhood_one , current_solution = " << current_solution.cost << setw(10) << setiosflags(ios::left) << " solution = " << solution.cost << endl; 417 if (current_solution.cost < solution.cost) 418 { 419 solution = current_solution; 420 l = 0; 421 } 422 break; 423 case 2: 424 neighborhood_two(current_solution, cities); 425 cout << setw(45) << setiosflags(ios::left) << "Now in neighborhood_two , current_solution = " << current_solution.cost << setw(10) << setiosflags(ios::left) << " solution = " << solution.cost << endl; 426 if (current_solution.cost < solution.cost) 427 { 428 solution = current_solution; 429 l = 0; 430 } 431 break; 432 433 default: 434 return; 435 } 436 l++; 437 438 } 439 440 } 441 442 //将城市序列分成4块,然后按块重新打乱顺序。 443 //用于扰动函数 444 void double_bridge_move(int * cities_permutation) 445 { 446 int pos1 = 1 + rand() % (CITY_SIZE / 4); 447 int pos2 = pos1 + 1 + rand() % (CITY_SIZE / 4); 448 int pos3 = pos2 + 1 + rand() % (CITY_SIZE / 4); 449 450 int i; 451 vector<int> v; 452 //第一块 453 for (i = 0; i < pos1; i++) 454 { 455 v.push_back(cities_permutation[i]); 456 } 457 458 //第二块 459 for (i = pos3; i < CITY_SIZE; i++) 460 { 461 v.push_back(cities_permutation[i]); 462 } 463 //第三块 464 for (i = pos2; i < pos3; i++) 465 { 466 v.push_back(cities_permutation[i]); 467 } 468 469 //第四块 470 for (i = pos1; i < pos2; i++) 471 { 472 v.push_back(cities_permutation[i]); 473 } 474 475 476 for (i = 0; i < (int)v.size(); i++) 477 { 478 cities_permutation[i] = v[i]; 479 } 480 481 482 } 483 484 //抖动 485 void shaking(SOLUTION &solution, CITIES *cities) 486 { 487 double_bridge_move(solution.permutation); 488 solution.cost = cost_total(solution.permutation, cities); 489 } 490 491 492 void variable_neighborhood_search(SOLUTION & best_solution, CITIES * cities) 493 { 494 495 int max_iterations = 5; 496 497 int count = 0, it = 0; 498 499 SOLUTION current_solution = best_solution; 500 501 //算法开始 502 do 503 { 504 cout << endl << "\t\tAlgorithm VNS iterated " << it+1 << " times" << endl; 505 count++; 506 it++; 507 shaking(current_solution, cities); 508 509 variable_neighborhood_descent(current_solution, cities); 510 511 if (current_solution.cost < best_solution.cost) 512 { 513 best_solution = current_solution; 514 count = 0; 515 } 516 517 cout << "\t\t全局best_solution = " << best_solution.cost << endl; 518 519 } while (count <= max_iterations); 520 521 522 } 523 524 525 int main() 526 { 527 528 srand((unsigned) time(0)); 529 530 SOLUTION best_solution; 531 532 random_permutation(best_solution.permutation); 533 best_solution.cost = cost_total(best_solution.permutation, berlin52); 534 535 cout << "初始总路线长度 = " << best_solution.cost << endl; 536 537 variable_neighborhood_search(best_solution, berlin52); 538 539 cout << endl << endl << "搜索完成! 最优路线总长度 = " << best_solution.cost << endl; 540 cout << "最优访问城市序列如下:" << endl; 541 for (int i = 0; i < CITY_SIZE; i++) 542 { 543 cout << setw(4) << setiosflags(ios::left) << best_solution.permutation[i]; 544 } 545 546 cout << endl << endl; 547 548 return 0; 549 }
程序结果:
04 变邻域搜索解决01背包问题
//TO DO
1#include
2#include
3#include
4#include
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 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) <" ";
272 if ((i + 1) % 25 == 0)
273 {
274 cout << endl;
275 }
276 }
277
278 cout << "\n石头价值为:" << 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}
程序结果: