用遗传算法解决子集和问题

浅谈遗传算法:https://www.cnblogs.com/AKMer/p/9479890.html
子集和问题:

Description

给你\(n\)个数,问是否存在一个子集使得子集中数字之和等于\(m\),为了增加难度,请输出小于等于\(m\)的子集和中最大的那一个。

Input

输入一共两行
第一行两个数\(n,m(n\leqslant1000,m\leqslant100000);\)
第二行\(n\)个数,每个数字不大于\(100\)

Output

一个数字,表示能用给出的数字拼出的小于等于\(m\)的最大的值

样例输入

5 10
2 2 6 5 4

样例输出

10
(2+2+6)或(6+4)

遗传算法经典题目,如果不会遗传算法的可以去看看顶上那篇博客。
数据其实可以再大一点的,只不过\(01\)背包只能造这个范围的数据了。
我自己造的数据:https://files.cnblogs.com/files/AKMer/%E5%AD%90%E9%9B%86%E5%92%8C%E9%97%AE%E9%A2%98.zip
遗传代数多一点就可以A掉了。不得不说,遗传算法的正确性还是挺鲁棒的。

时间复杂度:\(O(欧洲人)\)
空间复杂度:\(O(n)\)

代码如下:

#include <ctime>
#include <cstdio>
#include <algorithm>
using namespace std;

const int maxn=1005;
const int chr_cnt=20;
const int cross_cnt=10;

bool use[maxn],work=1;//use[i]记录第i个选还是不选,work表示遗传算法是否继续工作
int num[maxn],f[chr_cnt+5],g[chr_cnt+5],f1[chr_cnt+5];//num记录第i个数字,f记录第i条染色体的估价函数,f1用来缓存f,g存确定选择法下第i条染色体会被选择几次
int n,m,sum,dis,ans,var_cnt,tim;//n表示数字个数,m表示所求之和,sum记录所有数字之和,dis记录当前种群最优解离目标m差多少。
//ans记录最后答案,var_cnt记录变异次数,tim存连续最优解不变的代数

int read() {
    int x=0,f=1;char ch=getchar();
    for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
    for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
    return x*f;
}//快读

int random(int limit) {
    return rand()%limit;
}//rand出一个[0,limit-1]之内的数

struct Chromosome {
    int gene[maxn];//用一个长度为n的01串记录该染色体
}chr[chr_cnt+5],new_chr[chr_cnt+5];

void Initialization() {
    if(sum<=m) {
        for(int i=1;i<=n;i++)
            use[i]=1;
        return;//如果总和加起来都不超过m就直接全选带走
    }
    int pps=sum/m+1;
    for(int i=1;i<=chr_cnt;i++) {
        for(int j=1;j<=n;j++)
            if(!random(pps))chr[i].gene[j]=1;
            else chr[i].gene[j]=0;
    }dis=m;//随机20条染色体
}

void select() {
    for(int i=1;i<=chr_cnt;i++) {
        f[i]=0;
        for(int j=1;j<=n;j++)
            f[i]+=num[j]*chr[i].gene[j];//先令f[i]=i号染色体表示的总和
        if(f[i]<=m&&(m-f[i]<dis)) {//如果更新了最优解就更新
            dis=m-f[i];tim=0;
            for(int k=1;k<=n;k++)
                use[k]=chr[i].gene[k];//更新use
        }
        if(f[i]<=m)f[i]=sum+(m-(m-f[i]));//这样设计可以使得低于m并且靠近m的数适应度较其他都大
        else f[i]=sum-(f[i]-m);//因为大于m明显不优,所以往小了设
        if(!dis) {work=0;return;}//如果出最优解了就return
    }
    tim++;int res=0;
    for(int i=1;i<=n;i++)
        res+=f[i];
    int cnt=0;//cnt存新一代种群的染色体条数
    for(int i=1;i<=chr_cnt;i++) {
        g[i]=1.0*f[i]/res*chr_cnt;
        cnt+=g[i];
    }//求g数组
    for(int i=1;i<=chr_cnt;i++)
        f1[i]=f[i];
    while(cnt<chr_cnt)g[random(chr_cnt)+1]++,cnt++;
    cnt=0;
    for(int i=1;i<=chr_cnt;i++) {
        for(int j=1;j<=g[i];j++) {
            new_chr[++cnt]=chr[i],f[cnt]=f1[i];
            if(cnt==chr_cnt)break;
        }
        if(cnt==chr_cnt)break;
    }
    for(int i=1;i<=chr_cnt;i++)
        chr[i]=new_chr[i];//以上是确定性选择法
}

int calc(Chromosome u) {//计算染色体u的适应度
    int res=0;
    for(int i=1;i<=n;i++)
        res+=u.gene[i]*num[i];
    if(res<=m)res=sum+(m-(m-res));
    else res=sum-(res-m);
    return res;//意思如select中所述
}

bool check(int u,int v,int l,int r) {//判断交换u号染色体和v号的区间[l,r]之后是否会使两者的适应度都降低
    Chromosome a=chr[u],b=chr[v];
    for(int i=l;i<=r;i++)
        swap(a.gene[i],b.gene[i]);//交换
    int tmpa=calc(a),tmpb=calc(b);
    return tmpa<f[u]&&tmpb<f[v];//判断
}

void cross() {//交叉
    for(int i=1;i<=cross_cnt;i++) {
        int u=random(chr_cnt)+1,v=random(chr_cnt)+1;
        int l=random(n)+1,r=random(n)+1;//random出要交换的染色体与区间
        if(l>r)swap(l,r);
        if(check(u,v,l,r))continue;//如果不优就放弃此次机会
        for(int i=l;i<=r;i++)
            swap(chr[u].gene[i],chr[v].gene[i]);
        f[u]=calc(chr[u]),f[v]=calc(chr[v]);//否则交换并且重新计算适应度
    }
}

void variety() {//变异
    for(int i=1;i<=var_cnt;i++) {
        int u=random(chr_cnt)+1,pos=random(n)+1;//random出要变异的染色体与基因位置
        Chromosome tmp=chr[u];tmp.gene[pos]^=1;
        if(calc(tmp)<f[u])continue;//如果变异后不优就放弃此次机会
        chr[u].gene[pos]^=1;
        f[u]=calc(chr[u]);//否则变异并且更新适应度
    }
}

void Genetic() {
    if(sum<=m)return;//如果全选还没有m那么大就直接出结果
    var_cnt=n*20*0.1;//变异概率为0.1
    while(work) {
        select();//选择
        if(tim>2000)return;//超过2000代就放弃治疗
        if(work) {
            cross();//交叉
            variety();//变异
        }
    }
}

int main() {
    srand(time(0));
    n=read();m=read();
    for(int i=1;i<=n;i++)
        num[i]=read(),sum+=num[i];//读入
    Initialization();//初始化
    Genetic();//遗传算法
    for(int i=1;i<=n;i++)
        if(use[i])ans+=num[i];
    printf("%d\n",ans);//出结果
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/AKMer/p/9487269.html