贪婪遗传算法求解背包问题


1. 背包问题的数学建模

最近在学习遗传算法的时候,发现传统的遗传算法在求解背包问题时,经常会出现求解结果收敛到局部最优解的情况,对于这种问题,查阅相关资料后大致了解到背包问题在用遗传算法求解时,需要加入贪婪算子修改算法,本文对这种算法会做一个详细介绍,文末附源码下载地址。
首先先对背包问题做一个大致描述,一个小偷去一户人家偷东西,他有一个容量为V的背包,这户人家的所有物品有两个属性,其一是大小 g o o d V i goodV_i goodVi,其二是价值 g o o d C i goodC_i goodCi,问,小偷要拿哪些物品才可以保证自己收获最大?
我们设这户人家总共有N件物品,然后设数组goodV[N]与goodC[N]分别表示每件物品的体积与价值。然后我们对这个问题可以做一个建模,对于收获最大的理解也就是背包里的所有物品价值和最大,但我们应该怎样表示这样一个目标函数呢?
我们为每个物品可以设一个标志位,这个标志位值为0或1,分别表示不拿与拿。这样我们很容易就可以写出问题的目标函数: max Σ i = 1 N x i g o o d C i \Sigma{^N_{i = 1}}x_igoodC_i Σi=1NxigoodCi ,这儿的 x i x_i xi表示的就是标志位。由于小偷背包空间有限,所以他不可能随意的拿,我们还必须得有约束条件,比如说,小偷拿的东西体积总量不超过背包总容量V,我们可以用数学表达式方便的描述出来: Σ i = 1 N x i g o o d V i < = V \Sigma{^N_{i = 1}}x_igoodV_i <= V Σi=1NxigoodVi<=V。我们将上述式子写在一起就可以得出背包问题的数学模型了:

  • max Σ i = 1 N x i g o o d C i \Sigma{^N_{i = 1}}x_igoodC_i Σi=1NxigoodCi
  • s.t. {
    - Σ i = 1 N x i g o o d V i < = V \Sigma{^N_{i = 1}}x_igoodV_i <= V Σi=1NxigoodVi<=V
    - x i x_i xi = {0,1}, i = {1, 2,…, N}
    }

2. 经典遗传算法的缺陷

对于这一模型使用遗传算法我们可以进行求解,首先需要编辑染色体序列,由于模型中只存在 x i x_i xi这一变量,所以我们可以设置染色体长度为N,染色体如下表所示:

染色体序号12N
基因内容0或10或10或1

我们设定种群规模为Q,使用如下伪代码可以容易的生成我们的初始群体:

for i = 1 to Q
    for j = 1 to N
        随机一个01的数赋给chromosome[i][j]

上面伪代码中的chromosome[i]表示的是一个染色体,Q个染色体构成我们的初始群体。在这,聪明的读者会意识到一个问题,随机生成的染色体能够每次都能满足约束条件吗?所以为了能够保证我们的染色体符合约束条件,我们在这只能做一些牺牲,也就是对不满足约束条件的种群进行重新初始化,直到符合约束条件为止。这时,经典遗传算法的缺陷也就体现出来了,由于为了能够随机出满足条件的染色体我们丢掉了太多其他染色体,在这些丢掉的染色体中极有可能修改一两个基因位就可以得到最优解,所以最后这种算法的结果很难得出全局最优解。
可行解域在上图中,圆圈中表示该问题全部可行解,红线表示的是我们随机出的可行解。

3. 贪婪遗传算法

为了能够保证种群的多样性,以至于我们能够在整个可行域内找解而不是仅仅局限在某条线或某个小范围内,我们不能删去不符合约束条件的解,而是应该通过修改这个解使得它能够留下来。这时,贪婪算子就可以发挥作用了,贪婪算法的思想是每次都拿对自己最有利的物品,直到不能拿为止。但什么样的物品是最有利的呢?体积小不一定价值大,价值大不一定体积下,这是很矛盾的两个属性。我们引出一个新的概念单位容量价值,顾名思义其就是每单位容量的价值,我们每次只需要拿单位容量价值最高的物品就好了。
好了,到此就只剩一个问题了,怎样将贪婪算子加入遗传算法呢?我们的方法是,对于不满足约束条件的解 ,我们使用贪婪算子进行一个改造,其实这个贪婪算子也可以叫做局部搜索,这个概念对遗传算法全局收敛具有及其重要的作用。我们给出下面伪代码用于描述贪婪算子与遗传算法的结合:

for i = 1 to Q
    for j = 1 to N
        随机一个01的数赋给chromosome[i][j]
    if chromosome[i]不满足约束条件
	对该染色体中基因位为1的物品按单位价值容量排序,seq[i]表示排序后每个位对应的在原染色体中位置
	for k = 1 to S //S表示位为1得个数
	    //每拿一件计算一下目前所拿物品的总体积
	    sum = sum + goodV[seq[i]]
	    //不能再拿了
	    if sum > V
	    	break;
	for k to S
	    //将不能拿的物品相应基因位置0
	    chromosome[seq[i]] = 0; 

通过上述代码我们最后可得到一个种群多样性很丰富的初始群体。
接着是交叉,我们通过设定一个交叉概率Pc(通常位0.6)选择要参与交叉的父代。我们使用两点交叉的方法,交叉位置可以通过随机函数产生。

染色体序号123456
基因内容011001
基因内容110011

对于上面两个染色体,交叉位置是2和4,我们可以得出结果:

染色体序号123456
基因内容010001
基因内容111011

其次是变异,变异我们可以对交叉后的个体的每一个基因位使用变异概率Pm(一般为0.02)决定是否要发生变异,变异操作是将1变为0,或者0变为1。
对于交叉编译后的子代其实我们必须对其也得用一次贪婪算子改造染色体使其符合约束条件。具体过程与前面类似,此处不在赘述。
最后是选择,我们从原始群体和后代群体中选择Q个个体创建新的种群,这也就是达尔文进化论中的优胜劣汰。具体方法是,先算出每个个体可以得到物品的总价值,将其作为一个判定依据,总价值越大,其被选中的概率也就最大。我们挑选总价值最大的前2个直接进入下一代群体,然后使用轮盘赌的方法挑选剩余的Q-2个个体。
轮盘赌的方法是计算每个个体被选中的概率,然后随机一个数,该数落在那个区间就选对应的个体,例如下面这个例子:

个体序号12345
p0.20.50.750.91

若随机数是0.55,我们选择个体3进入下一代。
循环上面几个操作直到不满足条件,条件是进化代数。

4. 测试结果

输入:
goodC = 220 208 198 192 180 180 165 162 160 158 155 130 125 122 120 118 115 110 105 101 100 100 98 96 95 90 88 82 80 77 75 73 72 70 69 66 65 63 60 58 56 50 30 20 15 10 8 5 3 1

goodV = 80 82 85 70 72 70 66 50 55 25 50 55 40 48 50 32 22 60 30 32 40 38 35 32 25 28 30 22 50 30 45 30 60 50 20 65 20 25 30 10 20 25 15 10 10 10 4 4 2 1

V = 1000

进化1000次输出:
goods = 1 1 0 1 0 1 0 1 1 1 1 0 1 1 0 1 1 0 1 1 0 1 1 1 1 1 1 1 0 1 0 0 0 0 1 0 1 0 0 1 1 0 0 0 0 0 1 0 0 0
money = 3103
sumV = 1000


matlab源码下载地址

  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值