【智能算法】变邻域搜索算法(Variable Neighborhood Search,VNS)超详细解析和TSP代码实例以及01背包代码实例

喜欢的话可以扫码关注我们的公众号哦,更多精彩尽在微信公众号【程序猿声】

00 目录

  • 局部搜索再次科普
  • 变邻域搜索
  • 造轮子写代码

01 局部搜索科普三连

虽然之前做的很多篇启发式的算法都有跟大家提过局部搜索这个概念,为了加深大家的印象,在变邻域主角登场之前还是给大家科普一下相关概念。热热身嘛~

1.1 局部搜索是什么玩意儿?

官方一点:局部搜索是解决最优化问题的一种启发式算法。对于某些计算起来非常复杂的最优化问题,比如各种NP完全问题,要找到最优解需要的时间随问题规模呈指数增长,因此诞生了各种启发式算法来退而求其次寻找次优解,是一种近似算法(Approximate algorithms),以时间换精度的思想。局部搜索就是其中的一种方法。

通俗一点:局部搜索算法是对一类算法的统称,符合其框架的算法很多,比如之前公众号推文中介绍的爬山法、模拟退火法和禁忌搜索算法都属于局部搜索算法。尽管各个算法在优化过程中的细节存在差异,但在优化流程上呈现出很大的共性。

它的基本原理是在临近解中迭代,使目标函数逐步优化,直至不能再优化为止。

1.2 局部搜索的过程

我们可以将局部搜索算法的统一框架描述为:

  1. 算法从一个或若干个初始解出发。
  2. 在算法参数控制下由当前状态的邻域中产生若干个候选解。
  3. 以某种策略在候选解中确定新的当前解。
  4. 伴随控制参数的调节,重复执行上述搜索过程,直至满足算法终止准则。
  5. 结束搜索过程并输出优化结果。

1.3 局部搜索的几大要素

局部搜索算法主要包含五大要素:

  1. 目标函数:用来判断解的优劣。
  2. 邻域的定义:根据不同问题,有着不同的邻域定义。
  3. 初始解的产生规则
  4. 新解的产生和接受规则
  5. 算法终止准则

其中前两个要素的定义和算法要解决的特定问题有关,而且不同的人对同一问题可能有完全不同的定义。后三个要素定义的不同则会产生各种不同的局部搜索算法,而它们的效率和最终解的质量也会有很大的差异。

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其实就是一个算法框架,它的过程描述如下:

  1. 初始解S,定义M个邻域,记为Nk(k = 1, 2, 3……m),i = 1。
  2. 使用邻域结构Ni进行搜索,直到陷入局部最优解S′ 。
  3. 如果S′ 优于S,令S=S′,i=1; 否则,i++。
  4. 如果i≤m ,转步骤2。
  5. 输出最优解S。

我知道没图你们是不会点进来的……

VND的图解如下:

只说两点,再问自杀:

  • 当在本邻域搜索找不出一个比当前解更优的解的时候,我们就跳到下一个邻域继续进行搜索。如图中虚黑线所示。
  • 当在本邻域搜索找到了一个比当前解更优的解的时候,我们就跳回第一个邻域重新开始搜索。如图中红线所示。

之前我们把局部搜索比喻作爬山的过程,那么每变换一次邻域,也可以理解为切换了搜索的地形(landscape)。效果如下 :

每一次跳跃,得到都是一个新的世界……

伪代码描述如下:

2.5 shaking procedure

其实呀,这玩意儿。说白了就是一个扰动算子,类似于邻域动作的这么一个东西。通过这个算子,可以产生不同的邻居解。虽然名词很多看起来很高大上,扰动、抖动、邻域动作这几个本质上还是没有什么区别的。都是通过一定的规则,将一个解变换到另一个解而已。这里读者还是抓其本质,不要被表象所迷惑了就好。

2.6 VNS过程

在综合了前面这么多的知识以后,VNS的过程其实非常简单。可以描述为以下几步:

  1. 产生初始解s1。
  2. shaking s1,得到解s2。
  3. 对解s2进行VND,得到解s3。
  4. 如果达到边界条件,结束程序,输出最优解。否则跳回第二步。

结合伪代码,一目了然:

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
49565,575 },{ 25,185 },{ 345,750 },{ 945,685 },{ 845,655 },
50880,660 },{ 25,230 },{ 525,1000 },{ 580,1175 },{ 650,1130 },{ 1605,620 },
511220,580 },{ 1465,200 },{ 1530,5 },{ 845,680 },{ 725,370 },{ 145,665 },
52415,635 },{ 510,875 },{ 560,365 },{ 300,465 },{ 520,585 },{ 480,415 },
53835,625 },{ 975,580 },{ 1215,245 },{ 1320,315 },{ 1250,400 },{ 660,180 },
54410,250 },{ 420,555 },{ 575,665 },{ 1150,1160 },{ 700,580 },{ 685,595 },
55685,610 },{ 770,610 },{ 795,645 },{ 720,635 },{ 760,650 },{ 475,960 },
5695,260 },{ 875,920 },{ 700,500 },{ 555,815 },{ 830,485 },{ 1170,65 },
57830,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 << "\t\tAlgorithm 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 << "\t\t全局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 << "\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}

程序结果:

猜你喜欢

转载自www.cnblogs.com/infroad/p/9245684.html
今日推荐