一、01背包问题
背包问题(Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。相似问题经常出现在商业、组合数学,计算复杂性理论、密码学和应用数学等领域中。也可以将背包问题描述为决定性问题,即在总重量不超过W的前提下,总价值是否能达到V?它是在1978年由Merkel和Hellman提出的。
简单来说就是有N件物品和一个容量为V的背包。第i件物品的体积是b[i],价值是v[i]。求解将哪些物品装入背包可使价值总和最大。所谓01背包,表示每一个物品只有一个,要么装入,要么不装入。
二、遗传算法
遗传算法(Genetic Algorithms )是基于生物进化理论的原理发展起来的一种广为应用的、高效的随机搜索与优化的方法。其主要特点是群体搜索策略和群体中个体之间的信息交换,搜索不依赖于梯度信息。它是在70年代初期由美国密西根( Michigan )大学的霍兰( Holland )教授发展起来的。1975年霍兰教授发表了第一本比较系统论述遗传算法的专著《自然系统与人工系统中的适应性》(《 Adaptationin Natural and Artificial Systems 》)。遗传算法最初被研究的出发点不是为专门解决最优化问题而设计的,它与进化策略、进化规划共同构成了进化算法的主要框架,都是为当时人工智能的发展服务的。迄今为止,遗传算法是进化算法中最广为人知的算法。
遗传火算法的实施步骤如下(以目标函数求最小为例)。
第一步:初始化 t←0进化代数计数器;T是最大进化代数;随机生成M个个体作为初始群体P(t);
第二步:个体评价 计算P(t)中各个个体的适应度;
第三步:选择运算 将选择算子作用于群体;
第四步:交叉运算 将交叉算子作用于群体;
第五步:变异运算 将变异算子作用于群体,并通过以上运算得到下一代群体P(t + 1);
第六步:终止条件判断 t≦T:t← t+1 转到步骤2;t>T:终止 输出解。
遗传算法应用步骤:
1)确定决策变量及各种约束条件,即个体的表现型X和问题的解空间;
2)建立优化模型 (目标函数最大OR 最小) 数学描述形式 量化方法;
3)染色体编码方法;
4)解码方法;
5)个体适应度的量化评价方法 F(x)
6)设计遗传算子;
7)确定有关运行参数。
三、遗传算法求解01背包问题
对于背包问题,通常的处理方法是搜索,一般都是递归搜索或者动态规划的方法,关于这些算法我就不在这里做过多介绍了,在物品不是很多的时候用这些算法来处理背包问题效率上还是可以接受的,一旦物品过多(如50件物品)这些算法的效率就大打折扣了,因此采用一些智能的启发式搜索算法来处理就显得很有必要,本文就介绍了如何使用遗传算法求解01背包问题。
使用遗传算法第一件事情就是确定染色编码方式,它根据不同的问题模型使用不同编码方式,有二进制编码也有整数编码和浮点数编码,面对01背包问题,很明显使用二进制编码,因为物品只有选中与不选中两种状态,5个物品,用五个01组成的编码来表示,这是染色体长度就是5,如:11100表示前三个物品被选中,后两个物品没被选中;遗传算法的第二个要点就是评价函数,01背包问题的评价函数很简单,就是选中的物品的价值之和,如:11100的评价值就是前三个选中物品的价值之和,当然还有前三个物品体积不能超过背包容量这个约束条件,一旦超出容量那么它的评价值就是0了。清楚了解了这些,咱们就可以按照上面的遗传算法框架来进行编程了。具体代码如下:
package noah;
import java.io.IOException;
import java.util.Random;
public class GA2 {
private int[] v = { 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};// 物品价值
private int[] b = {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};// 物品体积
private int pb = 1000;// 背包容积
private int LL; // 染色体长度
private int scale;// 种群规模
private int MAX_GEN; // 运行代数
private int bestT;// 最佳出现代数
private int bestLength; // 最佳编码价值
private int[] bestTour; // 最佳编码
// 初始种群,父代种群,行数表示种群规模,一行代表一个个体,即染色体,列表示染色体基因片段
private int[][] oldPopulation;
private int[][] newPopulation;// 新的种群,子代种群
private int[] fitness;// 种群适应度,表示种群中各个个体的适应度
private float[] Pi;// 种群中各个个体的累计概率
private float Pc;// 交叉概率
private float Pm;// 变异概率
private int t;// 当前代数
private Random random;
// 种群规模,染色体长度,最大代数,交叉率,变异率
public GA2(int s, int l, int g, float c, float m) {
scale = s;
LL = l;
MAX_GEN = g;
Pc = c;
Pm = m;
}
private void init() throws IOException {
bestLength = 0;
bestTour = new int[LL];
bestT = 0;
t = 0;
newPopulation = new int[scale][LL];
oldPopulation = new int[scale][LL];
fitness = new int[scale];
Pi = new float[scale];
random = new Random(System.currentTimeMillis());
}
// 初始化种群
void initGroup() {
int k, i;
for (k = 0; k < scale; k++)// 种群数
{
// 01编码
for (i = 0; i < LL; i++) {
oldPopulation[k][i] = random.nextInt(65535) % 2;
}
}
}
public int evaluate(int[] chromosome) {
// 010110
int vv = 0;
int bb = 0;
// 染色体,起始城市,城市1,城市2...城市n
for (int i = 0; i < LL; i++) {
if (chromosome[i] == 1) {
vv += v[i];
bb += b[i];
}
}
if (bb > pb) {
// 超出背包体积
return 0;
} else {
return vv;
}
}
// 计算种群中各个个体的累积概率,前提是已经计算出各个个体的适应度fitness[max],作为赌轮选择策略一部分,Pi[max]
void countRate() {
int k;
double sumFitness = 0;// 适应度总和
int[] tempf = new int[scale];
for (k = 0; k < scale; k++) {
tempf[k] = fitness[k];
sumFitness += tempf[k];
}
Pi[0] = (float) (tempf[0] / sumFitness);
for (k = 1; k < scale; k++) {
Pi[k] = (float) (tempf[k] / sumFitness + Pi[k - 1]);
}
}
// 挑选某代种群中适应度最高的个体,直接复制到子代中
// 前提是已经计算出各个个体的适应度Fitness[max]
public void selectBestGh() {
int k, i, maxid;
int maxevaluation;
maxid = 0;
maxevaluation = fitness[0];
for (k = 1; k < scale; k++) {
if (maxevaluation < fitness[k]) {
maxevaluation = fitness[k];
maxid = k;
}
}
if (bestLength < maxevaluation) {
bestLength = maxevaluation;
bestT = t;// 最好的染色体出现的代数;
for (i = 0; i < LL; i++) {
bestTour[i] = oldPopulation[maxid][i];
}
}
// 复制染色体,k表示新染色体在种群中的位置,kk表示旧的染色体在种群中的位置
copyGh(0, maxid);// 将当代种群中适应度最高的染色体k复制到新种群中,排在第一位0
}
// 复制染色体,k表示新染色体在种群中的位置,kk表示旧的染色体在种群中的位置
public void copyGh(int k, int kk) {
int i;
for (i = 0; i < LL; i++) {
newPopulation[k][i] = oldPopulation[kk][i];
}
}
// 赌轮选择策略挑选
public void select() {
int k, i, selectId;
float ran1;
for (k = 1; k < scale; k++) {
ran1 = (float) (random.nextInt(65535) % 1000 / 1000.0);
// System.out.println("概率"+ran1);
// 产生方式
for (i = 0; i < scale; i++) {
if (ran1 <= Pi[i]) {
break;
}
}
selectId = i;
copyGh(k, selectId);
}
}
public void evolution() {
int k;
// 挑选某代种群中适应度最高的个体
selectBestGh();
// 赌轮选择策略挑选scale-1个下一代个体
select();
float r;
// 交叉方法
for (k = 0; k < scale; k = k + 2) {
r = random.nextFloat();// /产生概率
// System.out.println("交叉率..." + r);
if (r < Pc) {
// System.out.println(k + "与" + k + 1 + "进行交叉...");
OXCross(k, k + 1);// 进行交叉
} else {
r = random.nextFloat();// /产生概率
// System.out.println("变异率1..." + r);
// 变异
if (r < Pm) {
// System.out.println(k + "变异...");
OnCVariation(k);
}
r = random.nextFloat();// /产生概率
// System.out.println("变异率2..." + r);
// 变异
if (r < Pm) {
// System.out.println(k + 1 + "变异...");
OnCVariation(k + 1);
}
}
}
}
// 两点交叉算子
void OXCross(int k1, int k2) {
int i, j, flag;
int ran1, ran2, temp = 0;
ran1 = random.nextInt(65535) % LL;
ran2 = random.nextInt(65535) % LL;
while (ran1 == ran2) {
ran2 = random.nextInt(65535) % LL;
}
if (ran1 > ran2)// 确保ran1
运行结果截图:
四、总结
以上只是遗传算法的最基本框架代码,或许你觉得遗传算法效率其实也不怎样,实际上基本的遗传算法是有很多不足的,如容易选入局部收敛,全局搜索能力不够强,但是基本的遗传算法是有很多可以改进的地方,如交叉算子的设计、变异算子的设计、选择策略等等,有关遗传算法个人觉得作为一种智能启发式搜索算法它甚至比别的普通算法(如动态规划)理解起来还容易,而且它特别容易与别的算法相结合,设计新的混合算法,另外在基本遗传算法的基础上还衍生出了很多别的算法,如免疫遗传算法,这些都是一些比较高级的算法。
注:本文部分内容来源于网络,但程序以及分析结果属于本人成果,转载请注明!