变邻域搜索算法解决0-1背包问题

本文是关于【数据魔术师】:干货 | 变邻域搜索算法解决0-1背包问题(Knapsack Problem)代码实例一文的个人学习笔记,改了一点原文中的代码,仅供个人参考
【数据魔术师】:干货 | 变邻域搜索算法解决0-1背包问题(Knapsack Problem)代码实例

一.0-1背包问题

1.1什么是0-1背包问题?

0-1 背包问题:给定 n 种物品和一个容量为 C 的背包,物品 i 的重量是 w_i,其价值为 v_i
问:应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?

1.2解决方案设计

假设我们面前有n种物品,那么我们可以将解决方案设置成一个一维数组selection[n]。数组weights[n]表示重量,数组values[n]表示价值。

  • selection[i] = 1 表示装入第i个物品。
  • selection[i] = 0 表示不装入第i个物品。
  • 总价值total_value = selection[i] * values[i]。 (i=1,2,3,4……n)
  • 总重量total_weight = selection[i] * weights[i]。(i=1,2,3,4……n)

二.变邻域搜索

2.1变领域如何变?

变邻域搜索算法的主要思想是:采用多个不同的邻域进行系统搜索。 首先采用最小的邻域搜索,当无法改进解时,则切换到稍大一点的邻域。如果能继续改进解,则退回到最小的邻域,否则继续切换到更大的邻域。

那么显而易见,需要多个不同的领域动作!

于是设计如下3种邻域动作

2.2领域动作

邻域动作1

将解决方案selection[n]的第i位取反(i=1,2,3,4……n)。

邻域动作2

对于解决方案selection[n],在第i (i=1,2,3,4……n)位取反的情况下,依次将第j (j=i+1,2,3,4……n)位也取反

邻域动作3

交换第i位和第i-3位的数。比如:

  • selection[0]和selection[n-3]交换。
  • selection[1]和selection[n-2]交换。
  • selection[2]和selection[n-1]交换。

三.关键代码及解释

对一个方案进行计算

//对该方案selection[N]进行评价,计算total_values和total_weights
void Evaluate_Solution(KP_Solution & x) {//初始化total_values和total_weightsx.total_values = 0;x.total_weights = 0;//计算total_values和total_weightsfor (int i = 0; i < n; i++) {x.total_values += x.selection[i] * values[i];x.total_weights += x.selection[i] * weights[i];}//超过背包最大容纳重量,说明该方案不满足最大容纳重量约束,价值设置为负数if (x.total_weights > maxWeight)x.total_values = maxWeight - x.total_weights; //这样赋值的原因:对于同样不满足最大容纳重量约束的解。超出的重量越少,解越优,权值也就越大。
}

利用邻域动作生成邻居解

//对x采取邻域动作flip生成邻居解
void neighborhood(KP_Solution &x, int flip) {//邻域动作1if (flip == 1) {nbrhood.clear();for (int i = 0; i < n; i++) {nbrhood.push_back(x);nbrhood[i].selection[i]^=1;}}//邻域动作2if (flip == 2) {nbrhood.clear();int a = 0;for (int i = 0; i < n; i++)for (int j = i+1; j < n; j++) {nbrhood.push_back(x);nbrhood[a].selection[i]^=1;nbrhood[a].selection[j]^=1;a++;}}//邻域动作3if (flip == 3) {nbrhood.clear();for (int i = 0; i < n; i++) {nbrhood.push_back(x);//其实下面这段可以直写成//swap(nbrhood[i].selection[i], nbrhood[i].selection[(n + i - 3)%n]);if ( i < 3)swap(nbrhood[i].selection[i], nbrhood[i].selection[n + i - 3]);elseswap(nbrhood[i].selection[i], nbrhood[i].selection[i - 3]);}}
}

随机生成一个解决方案

//随机生成解决方案 (不一定满足最大背包容量的约束条件)
void Random_Solution(KP_Solution &x) {x.total_values = 0;x.total_weights = 0;for (int i = 0; i < n; i++)    {double rate = (rand() % 100) / 100.0;//不选该物品的概率为0.8,选该物品的概率为0.2if ( rate < 0.8 )x.selection[i] = 0;elsex.selection[i] = 1;}
}

变领域搜索

变邻域搜索算法的主要思想是:采用多个不同的邻域进行系统搜索。首先采用最小的邻域搜索,当无法改进解时,则切换到稍大一点的邻域。如果能继续改进解,则退回到最小的邻域,否则继续切换到更大的邻域。

//通过变邻域搜索产生邻域解,并在邻域解中的选最优解作为新一代的解
void Variable_Neighborhood_Descent(KP_Solution &x) {int flip = 1;//初始化flip为1,表示一开始采取邻域动作1(也就是最小的邻域搜索) KP_Solution x_curr;while ( flip < MaxFlip + 1) {neighborhood(x, flip);//对x采取flip这个邻域动作进行搜索,得到邻域解 //找到 flip这个邻域动作得到邻域解中的最优解x_curr x_curr = nbrhood[0];Evaluate_Solution(x_curr);for(unsigned int i = 1; i < nbrhood.size(); i++) {solutionsChecked ++;Evaluate_Solution(nbrhood[i]);if (nbrhood[i].total_values > x_curr.total_values)x_curr = nbrhood[i];}//邻域复位if (x_curr.total_values > x.total_values) {// 当前flip这个邻域动作能改进解,则退回到最小的邻域x = x_curr;flip = 1;} else// 当前flip这个邻域动作不能改进解,则切换到更大的邻域flip ++;}
}

随机修改

//随机取反 selection中的一些数
void Shaking_Procdure(KP_Solution &x) {int num = (rand() %n) /10 + 3; // num为取反的个数:3 ~ 3+(n/10)个 for (int i = 0; i < num; i++) {int pos = rand() % n;//得到随机取反的位置pos x.selection[pos]^=1;}Evaluate_Solution(x);//计算x的total_values和total_weights
}

迭代多次搜索求得最优解

//迭代iteration次变邻域搜索,取最优解
void Variable_Neighborhood_Search(KP_Solution &x, int iteration) {KP_Solution best = x;//best为当前最优解,一开始best为初始解Variable_Neighborhood_Descent(best);for (int i = 0; i < iteration; i++) {Shaking_Procdure(x);//对第i代的最优解X进行随机修改(我个人觉得这个优化能否更快找到最优解还有待考察)Variable_Neighborhood_Descent(x);//对X进行变邻域搜索,生成新一代的邻域解if (best.total_values < x.total_values)//如果新一代邻域解当中的最优解x比当前最优解best更优,则更新best。best = x;}x = best;
}

四.完整代码

#include <bits/stdc++.h>
using namespace std;
#define N 210//n的上限
int n;// 物品的数量 每一个物品有0和1两种选择 0代表选择当前物品 1代表不选择当前物品
int Max_Iteration;//算法最大迭代次数
const int MaxFlip = 3;//邻域数量
int maxWeight;//背包最大容量
int solutionsChecked = 0;//记录已经检查的背包数量
int values[N],weights[N];//物品对应价值&&重量
struct KP_Solution {int selection[N];  //当前方案的物品选择情况 selection[i] == 0 or 1 <==> 不选择 or 选择 第i个物品int total_values;      //当前方案下物品总价值int total_weights;    //当前方案下物品总重量
} kpx; //kpx为当前解//对该方案selection[N]进行评价,计算total_values和total_weights
void Evaluate_Solution(KP_Solution & x) {//初始化total_values和total_weightsx.total_values = 0;x.total_weights = 0;//计算total_values和total_weightsfor (int i = 0; i < n; i++) {x.total_values += x.selection[i] * values[i];x.total_weights += x.selection[i] * weights[i];}//超过背包最大容纳重量,说明该方案不满足最大容纳重量约束,价值设置为负数if (x.total_weights > maxWeight)x.total_values = maxWeight - x.total_weights; //这样赋值的原因:对于同样不满足最大容纳重量约束的解。超出的重量越少,解越优,权值也就越大。
}
vector<KP_Solution> nbrhood;//用vector记录邻居解集合//对x采取邻域动作flip生成邻居解
void neighborhood(KP_Solution &x, int flip) {//邻域动作1if (flip == 1) {nbrhood.clear();for (int i = 0; i < n; i++) {nbrhood.push_back(x);nbrhood[i].selection[i]^=1;}}//邻域动作2if (flip == 2) {nbrhood.clear();int a = 0;for (int i = 0; i < n; i++)for (int j = i+1; j < n; j++) {nbrhood.push_back(x);nbrhood[a].selection[i]^=1;nbrhood[a].selection[j]^=1;a++;}}//邻域动作3if (flip == 3) {nbrhood.clear();for (int i = 0; i < n; i++) {nbrhood.push_back(x);//其实下面这段可以直写成//swap(nbrhood[i].selection[i], nbrhood[i].selection[(n + i - 3)%n]);if ( i < 3)swap(nbrhood[i].selection[i], nbrhood[i].selection[n + i - 3]);elseswap(nbrhood[i].selection[i], nbrhood[i].selection[i - 3]);}}
}
//随机生成解决方案 (不一定满足最大背包容量的约束条件)
void Random_Solution(KP_Solution &x) {x.total_values = 0;x.total_weights = 0;for (int i = 0; i < n; i++)    {double rate = (rand() % 100) / 100.0;//不选该物品的概率为0.8,选该物品的概率为0.2if ( rate < 0.8 )x.selection[i] = 0;elsex.selection[i] = 1;}
}
/*变邻域搜索算法的主要思想是:采用多个不同的邻域进行系统搜索。首先采用最小的邻域搜索,当无法改进解时,则切换到稍大一点的邻域。如果能继续改进解,则退回到最小的邻域,否则继续切换到更大的邻域。*/
//通过变邻域搜索产生邻域解,并在邻域解中的选最优解作为新一代的解
void Variable_Neighborhood_Descent(KP_Solution &x) {int flip = 1;//初始化flip为1,表示一开始采取邻域动作1(也就是最小的邻域搜索) KP_Solution x_curr;while ( flip < MaxFlip + 1) {neighborhood(x, flip);//对x采取flip这个邻域动作进行搜索,得到邻域解 //找到 flip这个邻域动作得到邻域解中的最优解x_curr x_curr = nbrhood[0];Evaluate_Solution(x_curr);for(unsigned int i = 1; i < nbrhood.size(); i++) {solutionsChecked ++;Evaluate_Solution(nbrhood[i]);if (nbrhood[i].total_values > x_curr.total_values)x_curr = nbrhood[i];}//邻域复位if (x_curr.total_values > x.total_values) {// 当前flip这个邻域动作能改进解,则退回到最小的邻域x = x_curr;flip = 1;} else// 当前flip这个邻域动作不能改进解,则切换到更大的邻域flip ++;}
}
//随机取反 selection中的一些数
void Shaking_Procdure(KP_Solution &x) {int num = (rand() %n) /10 + 3; // num为取反的个数:3 ~ 3+(n/10)个 for (int i = 0; i < num; i++) {int pos = rand() % n;//得到随机取反的位置pos x.selection[pos]^=1;}Evaluate_Solution(x);//计算x的total_values和total_weights
}//迭代iteration次变邻域搜索,取最优解
void Variable_Neighborhood_Search(KP_Solution &x, int iteration) {KP_Solution best = x;//best为当前最优解,一开始best为初始解Variable_Neighborhood_Descent(best);for (int i = 0; i < iteration; i++) {Shaking_Procdure(x);//对第i代的最优解X进行随机修改(我个人觉得这个优化能否更快找到最优解还有待考察)Variable_Neighborhood_Descent(x);//对X进行变邻域搜索,生成新一代的邻域解if (best.total_values < x.total_values)//如果新一代邻域解当中的最优解x比当前最优解best更优,则更新best。best = x;}x = best;
}
//数据输入
void read_in() {cout << "请输入背包最大容量为:" ;cin>> maxWeight;cout << "请输入石头个数为:" ;cin>> n;Max_Iteration=n*10;for(int i=0; i<n; i++) {cout << "请分别输入第"<<i+1<<"个石头的重量和价值为:" ;cin>> weights[i] >>values[i];}
}
int main() {read_in();//读入数据,并可视化数据 srand(time(0));//初始化随机数种子 Random_Solution(kpx);//先随机生成一个解 kpxVariable_Neighborhood_Search(kpx, Max_Iteration);//执行变领域搜索更新kpx,迭代次数为Max_Iterationcout << endl << "最终结果: 已检查的总方案数 = " << solutionsChecked << endl;cout << "找到最大价值为: " << kpx.total_values << endl;cout << "背包当前重量为: " << kpx.total_weights << endl;for (int i = 0; i < n; i++) {cout << kpx.selection[i] << "  ";if ((i+1) % 25 == 0)cout << endl;}return 0;
}

五.运行结果

运行时间比较大是因为我输入的时候在想怎么构造输入数据,实际运行速度非常快!


六.个人收获和体会

学习了一种新的智能优化算法:变邻域搜索

对于01背包的动态规划方法:复杂度是O(NV),如果背包最大容量非常非常大的话,动态规划做法的运行时间可能会接受不了。

所以对于没有要求一定要求最优解的问题,可以用这种变领域搜索,得到一个还不错的解。

变邻域搜索算法解决0-1背包问题相关推荐

  1. vns基本的变邻域搜索算法

    论文VARIABLE NEIGHBORHOOD SEARCH 伪代码: 变邻域搜索算法(VNS)就是一种改进型的局部搜索算法.它利用不同的动作构成的邻域结构进行交替搜索,在集中性和疏散性之间达到很好的 ...

  2. C语言动态规划法解决0/1背包问题(详细解答)

    动态规划法解决0/1背包问题(详细解答) 首先让我们回顾一下动态规划法的使用规则: 一..动态规划法的实现思路: 1.划分子问题:将元问题分解为若干个子问题,每一个子问题对应一个决策,并且子问题之间具 ...

  3. 分枝限界解决0/1背包问题

    1.分枝限界法的概述 采用广度优先产生状态空间树的结点,并使用剪枝函数的方法称为分枝限界法.按照广度优先的原则,一个活结点一旦成为扩展节点后,算法将立刻将符合条件的孩子节点添加进入队列. 2.状态空间 ...

  4. 【TSP问题】基于变邻域搜索算法VNS求解旅行商问题附matlab代码

    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信.

  5. 局部邻域搜索-爬山法,模拟退火,禁忌,迭代局部搜索,变邻域局部搜索的简单阐释

    原文来源: 局部搜索算法 - JiePro - 博客园 https://www.cnblogs.com/JiePro/p/Metaheuristics_0.html 局部搜索算法 目录: 1.数学定义 ...

  6. 【车间调度】变邻域遗传算法求解柔性作业车间调度问题

    本系列为自己学习调度相关知识的记录,如有误请指出,也欢迎调度方向的小伙伴加我好友共同交流. 混合优化算法优化策略 变邻域搜索算法是一种快速和有效的求解复杂组合优化问题的局部搜索算法,通过邻域结构的系统 ...

  7. 分枝定界法解0/1背包问题

    分枝定界法解0/1背包问题 关键词:分支定界.0-1背包 分枝定界法简介 分枝定界法按照树形结构来组织解空间,展开节点后,有两种策略: 策略一.把节点加入 FIFO 队列当中: 策略二.把节点加入到堆 ...

  8. 0/1背包问题(蛮力法)

    问题描述: 给定n个重量为{w1,w2,w3,....,wn}.价值为{v1,v2,v3,...,vn}的物品和一个容量为C的背包,0/1背包问题是求解这些物品中的一个最有价值的子集,并且要能够装到背 ...

  9. matlab改进大规模邻域搜索算法求解路径优化

    近年来,随着环境问题的日益突出,越来越多物流配送企业开始使用节能环保的电动物流车. 但是由于续航里程有限及充电设施布局不完善等问题,电动车并不能完全代替燃油车,所以大多物流企业目前主要采用电动车与燃油 ...

最新文章

  1. java unsafe获取指针_【实战Java高并发程序设计 1】Java中的指针:Unsafe类
  2. 这本1900页的机器学习数学全书火了!完整版开放下载
  3. 分析B站10万条弹幕后,发现了歪嘴战神的终极奥义!
  4. http://www.cnblogs.com/qtqq/p/5271164.html
  5. SQL删除重复的记录(只保留一条)
  6. [.net 面向对象编程基础] (13) 面向对象三大特性——多态
  7. OCS 2007 R2将前端加入到企业版池
  8. 《从0到1学习Flink》—— Apache Flink 介绍
  9. [置顶] 2013腾讯编程马拉松初赛第4场(3月24)(HDU 4520 HDU4521 HDU4522 HDU4523 HDU4524)...
  10. SpringMVC请求流程
  11. c#:细说时区、DateTime和DateTimeOffset在国际化中的应用
  12. Rokid祝明铭:大腿我们不抱,人机交互产品形态未定 | 变局者
  13. java 注释 代码,如何在Java中注释代码块
  14. (一)洞悉linux下的Netfilteriptables:什么是Netfilter?
  15. volatile详解
  16. comment hive_Hive中基本语法
  17. 男怕入错行 完美池宇峰畅谈创业点滴
  18. 亮剑java web_为什么《亮剑Java Web 项目开发案例导航》第二个项目运行不了?
  19. 2022-08-17 第一组 顾元皓
  20. 分销商城系统核心功能模块

热门文章

  1. python如何采集同花顺股票日度历史数据
  2. 导入excel 文件解析,一行数据,有一列分两行显示,解析时如何 合为一行?
  3. 【绿色版软件】出现应用程序无法启动,并行配置不正确
  4. 厌倦只是一瞬间的事 2012-03-29 09:54:01 明明该有许多话要交代下去有许多事要汇报上去,明明有很多事情要去做,可是偏偏都不想去。可是突然还是打算留在电脑前漫无目的地把时间都耗费在了
  5. Gvim开发环境配置笔记--Windows篇(转)
  6. 学位论文精读-hBMSCs在肿瘤微环境中分泌IL-6并上调IL-17水平协同促进DLBCL生长的研究
  7. 前辈们的网络攻城狮心得
  8. flask后台开发之数据库交互
  9. 吴裕雄--天生自然 诗经:声声慢·寻寻觅觅
  10. stm32驱动rgb屏电路图_stm32h7“点亮RGB屏幕”