遗传算法求解 01 背包问题
一、问题描述
01 背包问题属于组合优化问题的一个例子,求解 01 背包问题的过程可以被视作在很多可行解当中
求解一个最优解。01 背包问题的一般描述如下:
给定 n 个物品和一个背包,物品 i 的重量为 Wi,其价值为 Vi,背包的容量为 C。选择合适的物品装
入背包,使得背包中装入的物品的总价值最大。注意的一点是,背包内的物品的重量之和不能大于背包
的容量 C。在选择装入背包的物品时,对每种物品 i 只有两种选择:装入背包或者不装入背包,即只能
将物品 i 装入背包一次。称此类问题为 0/1 背包问题。
01 背包问题是 NP 问题,传统的解决方法有动态规划法、分支界限法、回溯法等等。传统的方法不
能有效地解决 01 背包问题。遗传算法(Genetic Algorithms)则是一种适合于在大量的可行解中搜索最
优(或次优)解的有效算法。
二、遗传算法
1、遗传算法的基本思想
遗传算法的搜索从一个被称作种群的候选解集开始,新的种群由旧的种群中产生以期得到更好的种
群。从旧种群中按照解的适应度来选择解以产生新的解;适应度越大,解被选择生成后代的机率也越大。
这个从已有种群中选择双亲并产生后代的迭代过程持续到遗传算法的停止条件满足为止。
2、遗传算法的基本元素。
遗传算法由以下几个原素组成:由染色体组成的种群,根据适应度进行选择以及交叉产生后代。
三、用遗传算法求解 01 背包问题
1、01 背包问题中染色体的表示。
用向量 X 来表示染色体,
X = {x1,x2,……,xn}。,xi∈{0,1},
xi=1 表示物品 i 装入了背包,xi =0 表示物品 i 未装入背包。
每个染色体对应其当前装入背包的物品的总价值和总重量。背包中物品的中价值代表了该物品的适
应度。
程序中定义了这样的一个结构来表示染色体:
typedef struct{
int Weight;
int Fitness;
int Gene[NUMG]; //用元素取值于定义域{0,1}的数组表示染色体。
//染色体代表的物品的总重量
//染色体代表的物品的价值(适应度)
}GENE;
2、遗传算法求解 01 背包问题时用到的参数。
POPSIZE:种群大小,即已知的可行解的个数。
NUMG:染色体中基因的个数,即物品的总数。
CAPACITY:背包的容量。
MAXB:二进制表示的染色体换算之后的最大十进制整数。用于随机产生一个整数,进而转换作染
色体。
SIM:染色体之间的相似度阈值。当染色体之间的相似度达到阈值时,算法即停止运行。
PC=1.0 :交叉概率为 100%。
PM=0.2 :变异概率为 20%,变异可以保证种群的多样性,从而防止算法收敛于某个局部解。
3、选择操作。
选择操作采用了赌轮选择(Roulette-wheel selection)的方法。函数 selectIndex()中包含了选择
染色体的具体过程。其流程图如图 1 所示。
图 1 赌轮选择流程图
程序中用一个 Begin 来代表当前赌轮上的指针的位置,End 则用来使赌轮循环转动。
用 summaryBE 表示当前赌轮上的指针转过的染色体的总价值。
用 summaryF 表示当前全部染色体的价值总和。
用 randProb 作为染色体选择的阈值。randProb 为介于[0,1]之间的随机数。
选择使 summaryBE/summaryF 大于阈值 randProb 的染色体作为要选择的染色体。
4、交叉操作
程序中采用了单点交叉的策略。如下程序代码所示:
for(int j=0; j
的适应性强的染色体。相应地取代下一代中适应性弱的染色体。
程序中精英策略由 keepBestParents 函数和 sortFiness 函数来实现。需要说明的是 sortFitness 仅仅对
种群的索引进行了排序。然后用父代中 20%的适应度较大的优秀染色体替换子代中 20%的适应性弱的
染色体。
6、变异操作
染色体的变异可以保持种群的多样性,防止最优解的丢失以及算法收敛到局部最优解。
变异操作由 mutation 函数实现。
首先产生一个介于 0 和 1 之间的随机数 prob,若 prob 小于 MP 则进行变异操作:随机产生一个位
置 partPos,然后变异前染色体的 partPos 位置的基因为 1,则变异为 0,否则变异为 1;相应地要进行适
应度和染色体容量的变化。
7、代际更新
代际之间的更新,即用新生成的种群代替取代旧的种群。这个操作在 updateGeneration 函数中实现,
同 时 这 个 操 作 使 用 了 前 面 提 及 的 精 英 策 略 。 实 际 上 , 父 代 种 群 中 优 秀 的 染 色 体 已 被 保 留 ,
updateGeneration 中只是用新种群中的优秀染色体来代替父代中的染色体。
由于对父代和子代的染色体都进行了排序,因此程序中将子代的 80%视作优秀,将父代中的前 20%
视作优秀。
8、算法的终止
程序中采用了一个 HASH 表来对子代种群的适应度进行 HASH 操作。HASH 表中的头结点纪录了
头结点所指向的单链表的信息。如下代码中的注释:
typedef struct Head{
int maxFitness;
int Count;
int Diff;
HASHNODE *Next;
//单链表中的最大的 Fitness
//HASH 到该结点的染色体的数目
//单链表中有多少不同的 Fitness
}HEAD;
用这样的一个 HASH 表结构可以只需遍历 HASH 表中的头结点,即可知当前的种群的适应度最大
的染色体是否集中。
具体地,如 checkFitness 函数中的下面几行代码:
index = maxFitness(hashTable);
double CPount = hashTable[index].Count/(double)POPSIZE;
double pDiff = hashTable[index].Diff/(double)POPSIZE;
if(CPount>=0.9 && pDiff<=0.1){
sameFlag = false;
}
如果当前 maxFitness 最大的头结点满足 if 语句中的判断条件,则 sameFlag 置为真,即算法停止迭
代的条件得到了满足。
TraverseHashTable 函数则用于遍历 HASH 表。
算法终止的另一个条件是迭代的次数。程序中设定了算法的最大迭代次数为 1000。
四、实验结果。
试验中用到的物品的重量和价值分别存储于以下两个数组之中。
int Weight[NUMG]={6,9,8,8,6, 1, 10,5,7, 1};
int Value[NUMG]={2,20,5,4,19,14,18,8,11,6};
父代种群存储于 parentGenome[NUMG]中,子代种群存储于 nextGenome[NUMG]中。程序的初始状
态和结束状态如下面的表格所示:
Weight
21
22
22
22
26
24
22
23
26
25
初始的种群
染色体中的基因
Value
52
23
40
53
45
53
53
25
48
29
初始的 HASH 表
0010110101
0011000101
0001100011
0100010110
1010011001
1100010011
0100010110
1011010000
1010110100
0111000000
头结点索引 maxFitness Count Diff 单链表中的结点内容
0
1
3
6
9
10
12
程序在运行了 16 次后停止运行。
52
53
29
45
48
23
25
1
4
1
1
1
1
1
1
2
1
1
1
1
1
停止时的种群
52:
53:40:
29:
45:
48:
23:
25:
Weight
29
29
29
29
29
29
29
28
29
29
染色体中的基因
Value
78
78
78
78
78
78
78
64
78
78
停止时的 HASH 表
0100110111
0100110111
0100110111
0100110111
0100110111
0100110111
0100110111
0100100111
0100110111
0100110111
头结点索引 maxFitness Count Diff 单链表中的结点内容
0
12
78:
64:
78
64
9
1
1
1
即可知当前 01 背包问题的最优解为
X ={0,1,0,0,1,1,0,1,1,1}
对应的最优值是 78,即当前能够装入背包的最大价值。