首先简单介绍一下这个问题。01背包问题就是将若干个体积和价值各不相同物体,放入一个容量有限的背包,使得背包里面的物品总价值最大。
01背包问题其他背包问题的特殊之处在于,每个物品只有选或者不选,而且只能选一次。这就让我们不由得想到旅行商问题,其限制条件是每个城市只能经过一次,与01背包问题的限制条件类似。而蚁群算法是解决旅行商问题的常用算法。所以我们组尝试用蚁群算法解决01背包问题。
那么什么是蚁群算法呢,简单地说,就是让蚁群去探路,如果一条路较短,单位时间内往返的蚂蚁就更多,留下的信息素就会更多。并且每只蚂蚁倾向于选择信息素浓度更高的路径,这样,会形成正反馈。最终蚁群找到了最佳的路线。
对于01背包问题这样一个特定的问题,如何将蚁群算法应用呢?我们将物品组成为图。我们设置和物品数同样多的蚂蚁,它们每爬过一个物品,就将其投入背包,直到在装入某个物品后,背包剩余容量无法装入蚂蚁随机选择的下一个物品,作为终点。将该轮所以蚂蚁选择的物品组合整合,取总价值最高者。增加该路径的信息素浓度,挥发其他路径的信息素浓度。
也就是说,我们将总价值高作为蚁群的启发因子,引导蚂蚁选择总价值较高的组合。在蚂蚁走过足够多轮后,总价值最高的一个组合不再变化,答案也就水落石出。
下面我们进入代码实现的部分。在完成了初始化后,代码进入外循环。外循环管理的是,当蚂蚁走过足够轮,或者计算出的总价值最高的组合已经连续100轮不改变之后,结束计算,输出答案。
内循环是代码的核心部分。当出现两种情况,一是所有物品都已经选择过了,二是存在未被选择的物品但是一旦选取,体积将溢出背包容量,显然该循环中蚂蚁的工作结束,进入下一次循环。
如果符合内循环进行的条件,首先我们将符合条件的物品整理在一个数组内。然后根据选择每个物品的路径的信息素含量,用公式赋予该路径一个期望启发式因子,也就是rate,然后结合我们设置的辅助值r,u,生成选择该路径的概率。这样就做到了在期望启发式因子的引导下,蚂蚁随机选取路径。不断地重复这个过程,直到内循环结束。
在一轮内循环结束后,我们将选出该轮的最优解,将该路径的信息素加强,将其余路径的信息素挥发。之后再进入下一轮内循环。在信息素的引导下,每次内循环的最优解将在大量的内循环后收敛,直到多次不发生改变,或者循环次数足够后,外循环结束,完成计算。保存最优解,输出答案。
蚁群算法的优点是设置参数较少,流程固定。相比于动态规划,蚁群算法虽然运行效率略低于动态规划,但是蚁群算法拥有基本模型,好理解,对于学习者来说是重要优点。
#include "iostream"
#include <fstream>
#include <string>
#include <time.h>
using namespace std;
#define M 8//蚂蚁数
#define P 0.2//信息素挥发率
#define MAX 70
int BRoute[MAX];//最优解
int BValue=0;//最优解的总价值
int BWeight;//最优解的总重量
int max_circle=500;//外循环最大次数
int antRoute[9][MAX];
int antValue[9];
void main(){
int n;//物品个数
int w_limit;//背包重量限制
int value[MAX];//各物品价值
int weight[MAX];//各物品重量
float inf[MAX][MAX];//信息素矩阵
int i,j;
// ************** 读数据 *************************//
string filestring;
cout<<"请输入测试文件名: ";
cin>>filestring;
ifstream inFile(filestring.c_str());
//报告错误
if(!inFile)
{
cerr<<"不能打开测试文件:"<<filestring<<endl;
exit(-1);
}
inFile>>n;
inFile>>w_limit;
value[0]=0;
weight[0]=0;
for (i=1; i<=n; i++)
{
inFile >> weight[i];
}
for (i=1; i<=n; i++)
{
inFile >> value[i];
}
//************************* 信息素矩阵初始化 ********************//
for(i=0;i<=n;i++)
inf[i][0]=0;
for(i=0;i<=n;i++){
for(j=1;j<=n;j++){
inf[i][j]=1;
}
}
//************************ 数据输出 ****************************//
cout<<"物品总数为:"<<n<<endl;
cout<<"背包重量限制为:"<<w_limit<<endl;
cout<<"各物品重量分别为:"<<endl;
for(i=0;i<=n;i++)
cout<< weight[i]<<" ";
cout<<endl<<endl;
cout<<"各物品价值分别为:"<<endl;
for(i=0;i<=n;i++)
cout<< value[i]<<" ";
cout<<endl<<endl;
srand((int)time(0)); //初始化随机种子
bool mark[MAX];
int no_modify=0;
for(int k=1;k<=max_circle && no_modify<100 ;k++){//外循环
// for(int k=1;k<=max_circle ;k++){//外循环
for(i=1;i<=M;i++){
antRoute[i][0]=0;
int Cur_n=0; //已选取物品个数
int Cur_w=0; //已选取物品总重量
int Cur_v=0; //已选取物品总价值
int Cur_ps=0; //记录当前选取物品的标号
for(j=1;j<=n;j++)
mark[j]=false; //作为物品是否选取的标志
bool finish=false;
int ok[MAX];
while(finish==false){
if(Cur_n==n){
antRoute[i][++Cur_n]=0;
antValue[i]=Cur_v;
finish=true;
}
else{
int ok_n=0;
for(j=1;j<=n;j++){
if(mark[j]==false &&(Cur_w+weight[j])<=w_limit){
ok[ok_n++]=j; //该数组用于存储满足条件的物品的标号
}
}
if(ok_n==0){ //无满足条件的物品
antRoute[i][++Cur_n]=0;
antValue[i]=Cur_v;
finish=true;
}
else{
//有满足条件的物品:按信息素来进行随机选取
float total=0;
float rate[MAX];
for(j=0;j<ok_n;j++){
float total=total+inf[Cur_ps][ok[j]];
}
for(j=0;j<ok_n;j++)
rate[j]=(inf[Cur_ps][ok[j]]/total);
bool choose=false;
while(choose==false){
double r=(double)(rand() % 1001) * 0.001f;
int u=(int)(rand()%ok_n);
if(rate[u]>r){
antRoute[i][++Cur_n]=ok[u];
Cur_ps=ok[u];
Cur_w+=weight[ok[u]];
Cur_v+=value[ok[u]];
mark[ok[u]]=true;
choose=true;
break;
}
/*
for(j=0;j<ok_n;j++)
if(leiji[j]>r){
antRoute[i][++Cur_n]=ok[j];
Cur_ps=ok[j];
Cur_w+=weight[ok[j]];
Cur_v+=value[ok[j]];
mark[ok[j]]=true;
// cout<<endl<<"选中"<<ok[j]<<endl;
choose=true;
break;
}*/
}
}//else
}//else
}//while
}//for:内循环
//与当前最优解比较
int temp=0;
for(i=1;i<=M;i++){
if(antValue[i]>BValue){
BValue=antValue[i];
temp=i;
}
}
if(temp==0)
no_modify++; //记录连续不改变的次数,达到一定值时停止外循环
if(temp!=0){
no_modify=0;
for(int s=0;s<MAX;s++){
BRoute[s]=antRoute[temp][s];
}
}
//信息素更新/
//挥发
for(i=0;i<=n;i++)
for(j=0;j<=n;j++){
inf[i][j]*=(1-P);
}
//最优解再进行信息素的增强
inf[BRoute[0]][BRoute[1]]+=P/n;
for(int s=1; s<MAX && BRoute[s]!=0;s++){
inf[BRoute[s]][BRoute[s+1]]+=P/n;
}
}//外循环
cout<<"外循环次数为:"<<k<<endl;
cout<<"最好的解是:";
for( k=1;k<MAX && BRoute[k]!=0;k++)
cout<<BRoute[k]<<" " ;
cout<<endl<<"其总价值为:"<<BValue<<endl;
BWeight=0;
for(int s=1;s<MAX && BRoute[s]!=0;s++)
BWeight=BWeight+weight[BRoute[s]];
cout<<"其总重量为:"<<BWeight<<endl;
}