zoukankan      html  css  js  c++  java
  • 用遗传算法解决子集和问题

    浅谈遗传算法:https://www.cnblogs.com/AKMer/p/9479890.html

    子集和问题:

    Description

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

    Input

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

    Output

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

    Sample Input

    5 10
    2 2 6 5 4

    Sample Output

    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
    ",ans);//出结果
    	return 0;
    }
    

    巧的是我后来看到一道题和这个题意思一模一样……有时间再来填坑。
    Joy OJ1340送礼物:http://www.joyoi.cn/problem/tyvj-1340

  • 相关阅读:
    Pyecharts之平行坐标系(Parallel)
    Pyecharts之饼图(Pie)
    Pyecharts之仪表盘(Gauge)
    Tri-Training: Exploiting Unlabeled Data Using Three Classifiers
    A web crawler design for data mining
    Toward Scalable Systems for Big Data Analytics: A Technology Tutorial (I
    Learning with Trees
    Markov Random Fields
    Data storage on the batch layer
    Galaxy Classification
  • 原文地址:https://www.cnblogs.com/AKMer/p/9487269.html
Copyright © 2011-2022 走看看