一、
问题描述
背包问题(Knapsack
problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。相似问题经常出现在商业、组合数学,计算复杂性理论、密码学和应用数学等领域中。也可以将背包问题描述为决定性问题,即在总重量不超过W的前提下,总价值是否能达到V?它是在1978年由Merkel和Hellman提出的。
二、
知识表示
1、遗传算法
遗传算法(Genetic
Algorithm)是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法,它最初由美国Michigan大学J.Holland教授于1975年首先提出来的,并出版了颇有影响的专著《Adaptation in Natural and Artificial
Systems》,GA这个名称才逐渐为人所知,J.Holland教授所提出的GA通常为简单遗传算法(SGA)。
遗传算法是从代表问题可能潜在的解集的一个种群(population)开始的,而一个种群则由经过基因(gene)编码的一定数目的个体(individual)组成。每个个体实际上是染色体(chromosome)带有特征的实体。染色体作为遗传物质的主要载体,即多个基因的集合,其内部表现(即基因型)是某种基因组合,它决定了个体的形状的外部表现,如黑头发的特征是由染色体中控制这一特征的某种基因组合决定的。因此,在一开始需要实现从表现型到基因型的映射即编码工作。由于仿照基因编码的工作很复杂,我们往往进行简化,如二进制编码,初代种群产生之后,按照适者生存和优胜劣汰的原理,逐代(generation)演化产生出越来越好的近似解,在每一代,根据问题域中个体的适应度(fitness)大小选择(selection)个体,并借助于自然遗传学的遗传算子(genetic operators)进行组合交叉(crossover)和变异(mutation),产生出代表新的解集的种群。这个过程将导致种群像自然进化一样的后生代种群比前代更加适应于环境,末代种群中的最优个体经过解码(decoding),可以作为问题近似最优解。
2、状态空间
所谓状态,
是指在一定的时空范围内,问题所涉及的人、物、时间等的布局关系。通常把问题的初始布局关系称为初始状态,问题解决时到达的状态叫目标状态。这两个状态之间存在差异,如何从初始状态到达目标状态就是对问题求解。在状态空间法中问题的求解通常是从初始状态到达目标状态的一条最佳路径,这条路径依靠搜索算法在状态空间中寻找,这就是状态空间法的核心所在。
在背包问题中,初始状态就是有一个空包,包的重量固定为W,有N个商品,每个商品的重量为Wi,价值Ci。目标状态就是将n(n<=N)个商品装入包里,包重不超过W,使得包中商品的总重量最大。状态空间就是将商品装入包的所有组合,本实验的解就是价值和最大的装包组合。
3、遗传算法中的抽象概念在背包问题的具体化
(1)基因:0或1,代表相应的商品选还是不选。
(2)染色体:本实验中固定有50个商品,所以染色体就是50个基因序列,也就是40个0、1串,代表了一种往包里装商品的组合。一个染色体例:0111101101011011110101110101010101011110。
(3)群体:一定数量的基因个体组成了群体(population),群体中个体的数量叫做群体大小。本实验的背包问题中,种群大小为100,代表100个往包里装商品的组合。
(4)适应度:各个个体对环境的适应程度叫做适应度。本实验的背包问题中,每染色体个体的适应度为选入包中的商品的价值和。
三、
算法实现
1、
数据结构
(1)
重要参数:
#define zhongqun_size 100
//种群的规模
#define pc 0.8
//杂交概率
#define pm 0.08
//变异概率
#define chrom_length 50
//染色体长度
#define max_daishu 1000
//最大进化代数
(2)
染色个体:
struct population
{
unsigned int chrom[chrom_length];
//染色体
double weight;
//背包重量
double fitness;
//适应度
unsigned int parent1,parent2,cross; //双亲、交叉点
};
(3)
种群:
//新生代种群、父代种群
struct population
oldpop[zhongqun_size],newpop[zhongqun_size];
2、
程序流程
(1)读取存取包的限种、商品的重要和价值的TXT文件;
(2)初始化种群;
(3)计算群体上每个个体的适应度值(Fitness) ;
(4)评估适应度,对当前群体P(t)中每个个体Pi计算其适应度F(Pi),适应度表示了该个体的性能好坏;
(5)依照Pc选择个体进行交叉操作 ;
(6)仿照Pm对繁殖个体进行变异操作
(7)没有满足某种停止条件,则转第3步,否则进入8
;
(8)输出种群中适应度值最优的个体。
四、
实验结果分析
通过多次测试,工程实现遗传算法的背包问题,能够生成较优的装包组合。
五、
程序清单
#include "stdafx.h"
#include
#include
// 重要常量参数
#define zhongqun_size 100
//种群的规模
#define pc 0.8
//杂交概率
#define pm 0.08
//变异概率
#define chrom_length 50
//染色体长度
#define max_daishu 1000
//最大进化代数
struct population
{
unsigned int chrom[chrom_length];
//染色体
double weight;
//背包重量
double fitness;
//适应度
unsigned int parent1,parent2,cross; //双亲、交叉点
};
//新生代种群、父代种群
struct population
oldpop[zhongqun_size],newpop[zhongqun_size];
//背包问题中物体重量、收益、背包容量
int
weight[chrom_length],profit[chrom_length],bagweight;
//种群的总适应度、最小、最大、平均适应度
double
sumfitness,minfitness,maxfitness,avgfitness;
//计算适应度时使用的
惩罚函数系数
double alpha;
//一个种群中最大和最小适应度的个体
int
minpop,maxpop;
void
read_infor()
{
FILE *fp;
int j;
//获取背包问题信息文件
if ((fp=fopen("data.txt","r"))==NULL)
{
//读取文件失败
//AfxMessageBox("The file is not found",MB_OK,NULL);
return;
}
//读入物体收益信息
for (j=0;j
{
fscanf(fp,"%d",&profit[j]);
}
//读入物体重量信息
for (j=0;j
{
fscanf(fp,"%d",&weight[j]);
}
//读入背包容量
fscanf(fp,"%d",&bagweight);
fclose(fp);
}
//根据计算的个体重量,判断此个体是否该留在群体中
double cal_weight(unsigned int *chr)
{
int j;
double pop_weight;//背包重量
pop_weight=0;
for (j=0;j
{
pop_weight=pop_weight+(*chr)*weight[j];
chr++;
}
return pop_weight;
}
double cal_fit(unsigned int *chr)
{
int j;
double pop_profit;//适应度
pop_profit=0;
//
pop_weight=0;
for (j=0;j
{
pop_profit=pop_profit+(*chr)*profit[j];
//
pop_weight=pop_weight+(*chr)*weight[j];
chr++;
}
return pop_profit;
}
void
statistics(struct population *pop)
{
int
i;
double
tmp_fit;
sumfitness=pop[0].fitness;
minfitness=pop[0].fitness;
minpop=0;
maxfitness=pop[0].fitness;
maxpop=0;
for
(i=1;i
{
//计算种群的总适应度
sumfitness=sumfitness+pop[i].fitness;
tmp_fit=pop[i].fitness;
//选择种群中最大适应度的个体
if
((tmp_fit>maxfitness)&&((int)(tmp_fit*10)==0))
{
maxfitness=pop[i].fitness;
maxpop=i;
}
//选择种群中最小适应度的个体
if
(tmp_fit
{
minfitness=pop[i].fitness;
minpop=i;
}
//计算平均适应度
avgfitness=sumfitness/(float)zhongqun_size;
}
//
printf("\nthe max pop = %d;",maxpop);
//
printf("\nthe min pop = %d;",minpop);
//
printf("\nthe sumfitness = %f\n",sumfitness);
}
//报告种群信息
void
report(struct population *pop,int gen)
{
int j;
int
pop_weight=0;
printf("the generation is %d.\n",gen); //输出种群的代数
//输出种群中最大适应度个体的染色体信息
printf("The population's chrom is: \n");
for
(j=0;j
{
if
(j%5==0)
{ printf("
");}
printf("",pop[maxpop].chrom[j]);
}
//输出群体中最大适应度
printf("\nThe population's max fitness is
%d.",(int)pop[maxpop].fitness);
printf("\nThe knapsack weight is
%d.\n\n",(int)pop[maxpop].weight);
}
void
initpop()
{
int i,j,ispop;
double tmpWeight;
//变量用于判断是否为满足条件的个体
ispop=false;
//生成zhongqun_size个种群个体
for(i=0;i
{
while (!ispop)
{
for(j=0;j
{
oldpop[i].chrom[j]=rand()%2;
//随机生成个体的染色体
oldpop[i].parent1=0; //双亲
oldpop[i].parent2=0;
oldpop[i].cross=0;
//交叉点
}
//选择重量小于背包容量的个体,即满足条件
tmpWeight=cal_weight(oldpop[i].chrom);
if (tmpWeight<=bagweight)
{
oldpop[i].fitness=cal_fit(oldpop[i].chrom);
oldpop[i].weight=tmpWeight;
oldpop[i].parent1=0;
oldpop[i].parent2=0;
oldpop[i].cross=0;
ispop=true;
}
}
//此个体可以加入到种群中
ispop=false;
}
}
//概率选择试验
int
execise(double probability)
{
double pp;
//如果生成随机数大于相应的概率则返回真,否则试验不成功
pp=(double)(rand()
001/20000.0);
if
(pp<=probability) return 1;
return 0;
}
// 选择进行交叉操作的个体
int
selection(int pop)
{
double wheel_pos,rand_Number,partsum;
int parent;
//赌轮法选择
rand_Number=(rand() 01)/2000.0;
wheel_pos=rand_Number*sumfitness; //赌轮大小
partsum=0;
parent=0;
do{
partsum=partsum+oldpop[parent].fitness;
parent=parent+1;
}
while (partsum
&&
parent
return parent-1;
}
int
crossover(unsigned int *parent1,unsigned int *parent2,int
i)
{
int
j,cross_pos;
if
(execise(pc))
{
//生成交叉位置0,1,...(chrom_length-2)
cross_pos=rand()%(chrom_length-1);
}
else
cross_pos=chrom_length-1;
for
(j=0;j<=cross_pos;j++)
{
//保留复制;
//包括在概率选择不成功时,父体完全保留
newpop[i].chrom[j]=parent1[j];
}
for(j=cross_pos+1;j<=(chrom_length-1);j++)
{
//从交叉点开始交叉
newpop[i].chrom[j]=parent2[j];
}
//记录交叉位置
newpop[i].cross=cross_pos;
return 1;
}
int
mutation(unsigned int alleles)
{
if
(execise(pm))
{
if (alleles)
alleles=0;
else alleles=1;
}
//返回变异值,或者返回原值
return alleles;
}
void
generation()
{
unsigned int i,j,mate1,mate2;
double tmpWeight;
int ispop;//记录是否是符合条件的个体
i=0;
while (i
{
ispop=false;
while (!ispop)
{
//选择
mate1=selection(i);
mate2=selection(i+1);
//交叉
crossover(oldpop[mate1].chrom,oldpop[mate2].chrom,i);
//变异
for (j=0;j
{
newpop[i].chrom[j]=mutation(newpop[i].chrom[j]);
}
//选择重量小于背包容量的个体,即满足条件
tmpWeight=cal_weight(newpop[i].chrom);
if (tmpWeight<=bagweight)
{
newpop[i].fitness=cal_fit(newpop[i].chrom);
newpop[i].weight=tmpWeight;
newpop[i].parent1=mate1;
newpop[i].parent2=mate2;
ispop=true;
}
}
//此个体可以加入到种群中
i=i+1;
}
}
void
main(int argc, char* argv[])
{
int gen,oldmaxpop,k;
double oldmax;
read_infor();//读入背包信息
gen=0;
srand( (unsigned)time( NULL ) );//置随机种子
initpop();
memcpy(&newpop,&oldpop,zhongqun_size*sizeof(struct
population));
statistics(newpop);//统计新生种群的信息
report(newpop,gen);
while(gen
{
gen=gen+1;
if (gen0==0)
{
srand( (unsigned)time( NULL ) );//置随机种子
}
oldmax=maxfitness;
oldmaxpop=maxpop;
generation();
statistics(newpop); //统计新生代种群信息
//如果新生代种群中个体的最大适应度小于老一代种群
//个体的最大适应度,则保存老一代种群个体的最大适应度
//否则报告新生代的最大适应度
if (maxfitness
{
for(k=0;k
newpop[minpop].chrom[k]=oldpop[oldmaxpop].chrom[k];
newpop[minpop].fitness=oldpop[oldmaxpop].fitness;
newpop[minpop].parent1=oldpop[oldmaxpop].parent1;
newpop[minpop].parent2=oldpop[oldmaxpop].parent2;
newpop[minpop].cross=oldpop[oldmaxpop].cross;
statistics(newpop);
}
else if (maxfitness>oldmax)
{
report(newpop,gen);
}
//保存新生代种群的信息到老一代种群信息空间
memcpy(&oldpop,&newpop,zhongqun_size*sizeof(struct
population));
}
printf("It is over.");
getch();
}