遗传算法求解01背包
这是我本学期人工智能的一次大作业,思想可以延续,但是代码仅适用于这种情况,如题。
题目描述
任务描述:有N件物品和一个容量为V的背包,第i件物品的重量是w[i],价值是v[i],求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值的总和最大。
现在有一个背包,可以放置80公斤的物品,有以下六件物品。
物品 | 重量 | 价值 |
---|---|---|
1 | 10 | 15 |
2 | 15 | 25 |
3 | 20 | 30 |
4 | 25 | 50 |
5 | 30 | 55 |
6 | 35 | 75 |
请用遗传算法求解上述背包问题,给出设计方案和结果。
01背包简单介绍
01背包是经典的DP问题,问题描述为:有一个容量为V
的背包和N
件物品,每件物品分别有各自的体积的价值,第i
件物品的体积为v[i]
,价值为w[i]
,每个物品只有1
件,问将哪些物品装入背包能使背包物品总体积不超过背包容积且背包内所有物品的总价值最大。
遗传算法简单介绍
遗传算法是通过模拟生物的遗传进化,通过物竞天择,适者生存的原理淘汰无法适应环境的个体,筛选出对环境适应度比较高的个体继续进行遗传进化的过程。通过这个模拟我们可以解决一些最优解问题。(在进化过程中产生的最优个体即为最优解)。
需要注意的是,遗传算法是一种随机算法,因此它求出来的并不一定是最优解,有时候它进化一代并不能得出最优解,因此也需要通过多代进化增加得出最优解的概率。
在生物遗传的过程中,会发生生物基因的变异和生物染色体交换,而这些在遗传算法中也有各自的描述,基因变异和染色体交换在遗传算法的思想中都是为了增加随机性,始得得出的最优解是问题的最优解的概率变大。
思路分析
种群
首先我们要基于遗传算法来思考这个问题,首先我们需要一个种群来进行遗传进化,种群有两个问题,一个是种群在遗传算法中代表什么,一个是 种群的个体数量我们应该设置为多大 。这个种群的作用是进化出题目所需的最优解,即我们的最优解其实是从种群中获取的,而且种群中的物种必须是同一类型的,因为不同物种之间会产生生殖隔离,无法生育,也就是说,既然种群中其中一个是最优解,那么种群中其他个体都会是一个解, 种群 就是一个 解空间 ,而种群中个体的数量也很明显了,就是 解的数量 。
染色体
我们已经知道了一个个体将会是问题的一个解,而宏观个体是由微观的染色体组成的,而参与遗传过程的也是染色体,因此我们可以将一个个体代替为一种染色体,那么一个染色体就是问题的一个解。
基因
每个染色体中都有若干基因,那么问题有三个,一个是 基因代表什么,一个是 每个染色体上应该有多少个基因,一个是 每个基因的取值是什么。这个需要我们通过实际问题去决定。就以本题为例,本题的问题是问将哪些物品装入背包可以得出最优解,那么我们每个解里面都将会是物品的一个存放方案,因此我们一个染色体的基因就代表一种物品的存放方案。那么每种存放方案中都会有6个物品的存与不存的状态,因此一个基因就代表着一个物品的存放状态,一个染色体中应有6个基因。而因为每个物品只有存与不存两种状态,因此我们可以用0表示不存,1表示存。每个基因的取值就是0或者1。
交叉
也叫做交配。交叉就是用亲代通过染色体的交换得出子代的过程。这个过程是随机的,从种群中随机选取两个染色体作为亲代,然后随机选这两个染色体的一个节点进行染色体的交换。
变异
交叉是一对染色体之间的行为,而变异只是一个染色体的行为,在每一个染色体中的每个基因都有可能变异,因此我们可以设置一个变异的概率 p p p,遍历每个染色体的每个基因,并且随机给它们一个变异的数值 p i p_i pi,当该基因的变异数值 p i < p p_i < p pi<p时,该基因发生变异。
自然选择
既然要优胜劣汰,适者生存,那么我们就要给出一个适应度函数来计算每个个体的适应值,再对该个体进行筛选,优胜劣汰。那么在本问题下的适应度如何找到呢,从题目要求入手,题目总共有两个要求:
- 物品总体积不超过背包容积
- 背包内所有物品的总价值最大
那么我们可以把适应度分为两类,一类是存取物品总体积超过背包容积的染色体的适应度,另一类是存取物品总体积不超过背包容积的染色体的适应度。
对于第一类情况,因为已经不符合题意,所有适应度直接为0即可,对于第二类,它符合我们的第一点要求,因此只需要关心第二点要求,我们容易看出,在存取物品总体积不超过背包容积时,该染色体的价值越大越好。因此第二类染色体的适应度直接设置为该染色体的价值即可。
遗传(迭代)
上面已经说过,进化代数其实是为了提高得出最优解的概率,因此我们只需要对种群重复自然选择、交叉、变异的过程并算出每一代的最优解,并把第n
代最优解与前面共n - 1
代的最优解取最大值即可得出第n
代最优解。
代码
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <algorithm>
#include <vector>
using namespace std;
//用遗传算法求解01背包问题
long long popSize = 100;//种群大小,即已知可行解的总数
#define numG 6//染色体中基因的个数,即物品总数
#define pm 0.1//变异概率为0.1
int capacity = 80;//背包容量
pair<int,int> p[6] = //pair.first表示物体的重量,pair.second表示物体的价值
{{10,15},
{15,25},
{20,30},
{25,50},
{30,55},
{35,75}};
class Gene{
int weight;//染色体中物品总重量
int fitness;//染色体中物品总价值(适应度)
int gene[numG] = {0};//初始时所有染色体基因都为0,即每件物品都不选
public:
Gene(){
weight = 0;
fitness = 0;
for(int i = 0; i < numG; i++) gene[i] = rand() % 2;//随机初始化种子
}
~Gene(){};
int getW(){ //求出该染色体总重量
int w = 0;
for(int i = 0; i < numG; i++)
w += gene[i]*p[i].first;
weight = w;
return weight;
}
int getF(){ //求出该染色体总价值(适应度)
int f = 0;
for(int i = 0; i < 6; i++){
f += gene[i]*p[i].second;
}
fitness = f;
return fitness;
}
int Eval(){//适应度函数,若总体重超过背包承重,则适应度为0,否则实适应度为其价值
if(getW() > capacity) return 0;
return getF();
}
int getG_i(int i){//取出该染色体的第i个基因(i从0开始)
return gene[i];
}
void swap(Gene& B,int x){//交叉,x为染色体交叉的位置
for (int i = x; i < numG; ++i) {
if(getG_i(i) != B.getG_i(i)) gene[i] = 1 - getG_i(i);
}
}
void var(){//变异
for (int i = 0; i < numG; ++i) {
double pro = rand() % 100 /(double) 101;
if (pro < pm) gene[i] = 1 - gene[i];
}
}
// void printG(){//测试用,输出该染色体所有基因
// for (int i = 0; i < numG; ++i) {
// cout << gene[i] << ' ';
// }
// cout << endl;
// }
};
bool cmp(Gene A,Gene B){//排序比较函数
return A.Eval() < B.Eval();
}
//遗传算法步骤
//1、初始化群体,设置进化代数
int generation = 100;//进化代数
//2、适应性评价,保存最优染色体
Gene best;//最优染色体
//3、选择
//4、交配
//5、变异
//6、适应性评价,更新最优染色体
//7、判断进化是否满足终止条件,是则结束,否则回到第三步
int main() {
cout << popSize << endl;
srand((unsigned)time(0));
//1、初始化群体
Gene A[popSize];
//2、适应性评价,保存最优染色体
sort(A,A + popSize,cmp);//按适应度从小到大排序
best = (best.Eval() > A[popSize - 1].Eval()) ? best : A[popSize - 1];//最后一个就是适应度最大的染色体
while (generation --) {//终止条件,进化完100次
//3.选择
for (int i = 0; A[i].Eval() == 0; ++i) {//如果该染色体适应度为0,则重新随机分配一个染色体基因
Gene G;
A[i] = G;
}
//4、交叉
int n = popSize - 1;//用洗牌算法再0到99内找50对不重复的数
vector<int>q;
for(int i = 0; i < popSize; i++) q.push_back(i);
while (n){
int k = rand() % (n + 1);
swap(q[k],q[n]);
n --;
}
// for (int i = 0; i < popSize; ++i) {
// cout << q[i] << ' ';
// }
// cout << endl << endl;
for (int i = 0; i < popSize; i += 2) {//适应度最大的10个(或9个)染色体不参与交叉
int t = rand() % 6;//从0到5随机选出一个交叉点进行交叉
Gene temp = A[q[i]];
A[q[i]].swap(A[q[i + 1]], t);
A[q[i + 1]].swap(temp, t);
}
//5、变异
for (int i = 0; i < popSize - 10; ++i) {
A[i].var();
}
//6、适应性评价,更新最优染色体
sort(A, A + popSize, cmp);
best = (best.Eval() > A[popSize - 1].Eval()) ? best : A[popSize - 1];
}
cout << best.Eval();
return 0;
}