0-1背包问题
如何把这些物品发入这个有承重质量质量限制的背包中,在不超出背包最大限制的前提下,使得放入背包中的物品总价值最大。
1、问题描述
已知 l 个物品的质量及其价值分别为Wi(i=1,2,3…)和Vi(i=1,2,3…),背包的最大载重量为C。
则0-1背包问题可被描述为:选择哪些物品放入背包,使得背包在最大载重量限制之内所装物品的总价值最大?
0-1背包问题的数学模型如下:
max
∑
i
=
1
l
v
i
x
i
(1)
\ \max\sum_{i=1}^{l}{v_ix_i}\tag{1}\,
maxi=1∑lvixi(1)
max
∑
i
=
1
l
w
i
x
i
<
=
C
(2)
\ \max\sum_{i=1}^{l}{w_ix_i}<=C\tag{2}\,
maxi=1∑lwixi<=C(2)
x
i
∈
{
0
,
1
}
,
i
=
1
,
2
,
.
.
.
l
(3)
\\x_i\in\lbrace{0,1}\rbrace, i=1,2,...l\tag{3}\,
xi∈{0,1},i=1,2,...l(3)
上式中
x
i
\\x_i\,
xi是0-1决策变量,表示物品i是否被装包,如果装包,则
x
i
=
1
\\x_i=1\,
xi=1,否则,
x
i
=
0
\\x_i=0\,
xi=0
目标函数(1)表示最大化背包中物品的总价值,约束(2)限制装入背包物品的总质量不大于背包的最大承载量。
2、算法简介
假设上述问题中,一共只有5个物品,假设不考虑背包的最大载重量,我们利用简单的数学知识就可以得到一共有
C
5
0
+
C
5
1
+
C
5
2
+
C
5
3
+
C
5
4
+
C
5
5
=
32
\\C_5^0+C_5^1+C_5^2+C_5^3+C_5^4+C_5^5=32\,
C50+C51+C52+C53+C54+C55=32种装包方案。
当物品数目较少时,可以采用枚举法枚举出所有的装包方案,然后找到最优的装包方案。
但是物品数目增大时,很显然枚举法已经不适合求解该问题。针对这类问题,智能优化算法应运而生。
3、求解策略
3.1 编码
编码是GA最为重要的一个步骤,编码要合理,要体现待解决问题的特点。以本章0-1问题为例,这个问题的特点就是0和1,假设物品数目为n,那么一条染色体可以用n个数字(每个数字是0或是1)表示一个装包方案,其中每个数字被称为“基因”。当物品数目为n时,装包方案数目最多为
2
n
\\2^n\,
2n,所有最多有
2
n
\\2^n\,
2n条不同的染色体。
但是在编码过程中,一般情况下不会提前知晓问题的解。因为使用GA的目的是求得问题的解,所以染色体会根据物品数目n随机生成。
3.2 解码
编码是将装包方案转换成染色体,而解码是将染色体转换成装包方案,编码和解码其实是相反的过程。
3.3 约束处理
假如背包的最大载重量是6kg,这5个物品的质量和价值分别如下:
物品1:5kg,12元
物品2:2kg,8元
物品3:1kg,4元
物品4:4kg,15元
物品5:3kg,6元
因为在省城染色体时染色体在各位置上是随机取0或者1的,即一个染色体可能为11001,此时背包中物品总质量=物品1+物品2+物品5=10kg>6kg,此背包方案违反了背包的载重量约束
处理该约束的步骤:
(1)将已经装进背包中的物品按照性价比(性价比=价值/质量)由低到高排序;
(2)按照(1)步排序步骤,取走排在第一位的物品,检验背包中剩下的是否满足载重约束。如果满足,则将染色体中基因位上的数字1改为0,此时染色体修复完毕;如果不满足约束,先将染色体中基因位上的数字1改为0,然后取走排在第2位的物品,再次检验此时染色体中是否满足背包的载重量约束。循环往复,一直到满足背包的载重量约束位置,染色体初步修复完成。
(3)在第(2)步已经满足背包载重量约束的染色体,但此时背包可能还有剩余空间。这时,将此时未装包的物品按照性价比从高到低排序,然后按照该顺序依次将物品装进背包中。在装包过程中,将不满足约束的物品不装包,将满足约束的物品装包,并将染色体基因位上的数字0改为数字1,一直遍历到最后一个未装包的物品为止,染色体修复完毕。
3.4 适应度函数
装包的目的是在不违反载重量约束的前提下使背包中物品总价值最大化,可以用这个指标来评价染色体优劣。
在遗传算法中,评价指标的学名是适应度函数。
假设有n个物品,物品i的价值为
p
i
\\p_i\,
pi,那么适应度函数Fitness的计算公式为:
F
i
t
n
e
s
s
=
∑
i
=
1
n
p
i
,
(4)
\\Fitness\ =\ \sum_{i=1}^{n}p_i\tag{4},
Fitness = i=1∑npi,(4)
公式(4)中可以看出,适应度值越大,表明染色体越优。
3.5 种群初始化
假设种群数目为NIND,那么需要初始化NIND个个体(染色体)
如何初始化一个个体?
假设有n个物品,那么一个染色体的长度为n,即这个染色体由n个数字组成(每个数字都是0或1),初始化一个个体的步骤如下:
(1)随机生成n个数字(每个数字都是0或1),将此时的个体命名为Individual;
(2)检验Individual是否满足背包的载重量约束。如果满足约束,则个体Individual初始化完毕;如果不满足约束,则对个体Individual进行约束处理,约束处理结束后,个体Individual初始化完毕。
按照对一个个体初始化的方法,对NIND个个体全部进行初始化,初始化结束后,即完成对父代种群Chrom的初始化。
3.6 选择操作
常规思维是挑选出适应值大的个体,然后进行后续操作。但如果只挑选出适应度值大的个体,则很容易使整个种群在后续的进化操作中停滞不前,即陷入局部最优;
因此,在选择个体时,不能仅注意适应值大的个体,还需兼顾适应值小的个体。具体的方法就是轮盘赌选择策略,每次选择一个个体就转动一次轮盘赌转盘,指针指向的区域就是被选中的个体。
在自然界中,动物在繁衍后代时并不是可以百分百孕育出后代。因此,虽然种群中一共有一共有NIND个个体,但并不意味着需要选择出NIND个个体,可能选择出Nsel=NIND * GGAP(GGAP称为“代沟”,是一个大于0小于等于1的随机数)。子代种群SelCh只需要转动Nsel次转盘,选择出Nsel个个体。
在轮盘赌转盘中,每个个体对应一个被选中的概率。假设第i个个体的适应度值为
F
i
t
n
e
s
s
i
\\Fitness_i\,
Fitnessi,那么被选中概率的计算公式如下:
S
e
l
e
c
t
i
=
F
i
t
n
e
s
s
i
∑
i
=
1
N
I
N
D
F
i
t
n
e
s
s
i
(5)
\\{Select}_i=\frac{{\rm Fitness}_i}{\sum_{i=1}^{NIND}{\rm Fitness}_i}\tag{5}
Selecti=∑i=1NINDFitnessiFitnessi(5)
选择操作选择出的Nsel个个体会有重复,这是因为有些适应度值大的个体被选中的概率大而被选中多次。
3.7 交叉操作
在选择操作后,选择出的个体组成了新的种群,即子代种群。为了使种群向着适应度值增大的方向进化,需要改变个体上的 基因,只有基因发生改变,才可能会向着适应度值增大的方向前进。
当子代种群中的Nsle个个体都需要进行交叉操作时,因为一次交叉操作需要两个个体,所以按照顺序将这Nsel个个体分成Nsel/2组(如果Nsel为奇数,则在分组时不考虑第Nsel个个体,而是将Nsel-1个个体分成(Nsel-1)/2组),然后对每组的两个个体进行交叉操作。
3.8 变异操作
改变个体上基因的第二个操作是变异操作,变异操作在自然界只起到辅助作用。
3.9 重组操作
经过选择、交叉和变异得到Nsel操作得到Nsel个个体,因为父代种群数目为NIND,所以需要NIND-Nsel个个体才可以对父代种群Chrome中找出适应度值排在前NIND-Nsel位的NIND-Nsel个个体,然后添加到子代种群SelCh中。
假设NIND=5,Nsel=3,则重组操作如下图所示:
4、matlab程序实现
主函数程序:
tic
clear
clc
%% 创建数据
%各个物品的质量,单位kg
w=[80,82,85,70,72,70,82,75,78,45,49,76,45,35,94,49,76,79,84,74,76,63,...
35,26,52,12,56,78,16,52, 16,42,18,46,39,80,41,41,16,35,70,72,70,66,50,55,25, 50,55,40];
%各个物品的价值,单位元
p=[200,208,198,192,180,180,168,176,182,168,187,138,184,154,168,175,198,...
184,158,148,174,135, 126,156,123,145,164,145,134,164,134,174,102,149,134,...
156,172,164,101,154,192,180,180,165,162,160,158,155, 130,125];
cap=1000; %每个背包的载重量为1000kg
n=numel(p); %物品个数
%% 参数设置
NIND=500; %种群大小
MAXGEN=500; %迭代次数
Pc=0.9; %交叉概率
Pm=0.08; %变异概率
GGAP=0.9; %代沟
%% 初始化种群
Chrom=InitPop(NIND,n,w,p,cap);
%% 优化
gen=1;
bestIndividual=Chrom(1,:); %初始将初始种群中一个个体赋值给全局最优个体
bestObj=Individual_P_W(n,bestIndividual,p,w); %计算初始bestIndividual的物品总价值
BestObj=zeros(MAXGEN,1); %记录每次迭代过程中的最优适应度值
while gen<=MAXGEN
%% 计算适应度
Obj=Obj_Fun(Chrom,p,w); %计算每个染色体的物品总价值
FitnV=Obj; %适应度值=目标函数值=物品总价值
%% 选择
SelCh=Select(Chrom,FitnV,GGAP);
%% 交叉操作
SelCh=Crossover(SelCh,Pc);
%% 变异
SelCh=Mutate(SelCh,Pm);
%% 重插入子代的新种群
Chrom=Reins(Chrom,SelCh,Obj);
%% 将种群中不满足载重量约束的个体进行约束处理
Chrom=adjustChrom(Chrom,w,p,cap);
%% 记录每次迭代过程中最优目标函数值
[cur_bestObj,cur_bestIndex]=max(Obj); %在当前迭代中最优目标函数值以及对应个体的编号
cur_bestIndividual=Chrom(cur_bestIndex,:); %当前迭代中最优个体
%如果当前迭代中最优目标函数值大于等于全局最优目标函数值,则进行更新
if cur_bestObj>=bestObj
bestObj=cur_bestObj;
bestIndividual=cur_bestIndividual;
end
BestObj(gen,1)=bestObj; %记录每次迭代过程中最优目标函数值
%% 打印每次迭代过程中的全局最优解
disp(['第',num2str(gen),'次迭代的全局最优解为:',num2str(bestObj)]);
%% 更新迭代次数
gen=gen+1 ;
end
%% 画出迭代过程图
figure;
plot(BestObj,'LineWidth',1);
xlabel('迭代次数');
ylabel('目标函数值(物品总价值)');
%% 最终装进包中的物品序号
pack_item=find(bestIndividual==1);
%% 计算最优装包方案的物品总价值和总重量
[bestP,bestW]=Individual_P_W(n,bestIndividual,p,w);
toc