遗传算法解决01背包问题

遗传算法解决01背包问题

一、问题描述

01背包问题是组合优化问题的一个典型例子,它要求在许多可行解中找到一个最优解。

01背包问题的一般描述如下:给定一个固定的背包容量和一组物品,每个物品有一个重量和一个价值,要求从这组物品中选择一些放入背包,使得背包中物品的总价值最大,同时不超过背包的容量。

01背包问题的定义。

给定n个物品和一个背包,物品i的重量为Wi,其价值为Vi,背包的容量为C。目标是从这些物品中挑选一些放入背包,使得背包内物品的总价值最大化,同时不超过背包的容量限制C。在挑选物品时,对于每个物品i只能选择放入或不放入背包,不能重复放入或只放入一部分。

二、遗传算法

1、遗传算法的基本思想

遗传算法是一种模拟自然进化过程的优化方法,它从一个称为种群的候选解集合开始,通过不断地产生新的种群来寻找更优的解。

遗传算法的核心是选择、交叉和变异三种操作。

  • 选择是根据解的适应度来从旧种群中挑选出一些解作为双亲,适应度越高,被选中的概率越大。
  • 交叉是将两个双亲的部分特征进行组合,产生新的解作为后代。
  • 变异是对某些解的某些特征进行随机改变,增加种群的多样性。

这个迭代产生新种群的过程一直持续到遗传算法达到预设的终止条件为止。这就是遗传算法的基本原理。

2、遗传算法的基本元素。

遗传算法是由以下几个要素构成的:

  1. 一个由染色体编码的候选解组成的种群,每个染色体代表一个可行解;
  2. 一个根据染色体的适应度评估其优劣的适应度函数,适应度越高,染色体越优;
  3. 一个根据适应度选择染色体作为双亲的选择机制,适应度越高,被选中的概率越大;
  4. 一个通过交换双亲染色体的部分基因产生新染色体的交叉操作,交叉可以增加种群的多样性和创新性;
  5. 一个对某些染色体的某些基因进行随机变化的变异操作,变异可以防止种群陷入局部最优。

这些要素共同构成了遗传算法的基本框架。


例题1:

背包容量为9,物品重量 w={2,3,4,5},其价值是v={3,4,5,7}}。

1 编码:

针对01背包问题,我们设计了一种染色体编码方法:将待求解的n个决策变量
x 1 , x 2 , . . . , x n {x1,x2,...,xn} x1,x2,...,xn
表示为长度为n的二进制字符串
x [ j ] , j = 1 , 2 , … , n 。 x[j],j=1,2, …,n。 x[j]j=12n

x[j]=0表示第j个物品不放入背包,x[j]=1表示第j个物品放入背包。

例如:1010代表一个可行解,它表示将第1、3号物品放入背包,其余的物品则不放入。

2 生成初始种群:

在这个例子中,我们设定种群规模为4,也就是说种群由4个个体构成,每个个体可以用随机的方式生成。例如:
a1 = [1, 1, 1, 1]
a2 = [1, 0, 1, 0]
a3 = [1, 0, 0, 1]
a4 = [1, 0, 0, 0]

3 种群适应度计算:

按照下列公式计算种群中个体适应度:
T o t a l S i z e = Σ w i x i TotalSize=Σwixi TotalSize=Σwixi

F i t n e s s = { Σ v i x i T o t a l S i z e ≤ C Σ v i x i − a l p h a ∗ ( T o t a l S i z e − C ) T o t a l S i z e > C \left.Fitness=\left\{\begin{array}{ll}\Sigma v_ix_i&TotalSize\leq C\\\Sigma v_ix_i-alpha*(TotalSize-C)&TotalSize>C\end{array}\right.\right. Fitness={ΣvixiΣvixialpha(TotalSizeC)TotalSizeCTotalSize>C

公式的下半部分即为适应度的惩罚函数,其中参数alpha>1.0。在本例中我们取alpha=2。

a1 = [1, 1, 1, 1], totalSize = 14, totalValue = 19, fit = 9
a2 = [1, 0, 1, 0], totalSize = 6, totalValue = 8, fit = 8
a3 = [1, 0, 0, 1], totalSize = 7, totalValue = 10, fit = 10
a4 = [1, 0, 0, 0], totalSize = 2, totalValue = 3, fit = 3

4 选择:

为了确定每个个体在下一代种群中复制的数量,我们采用了一种与适应度成正比的概率选择方法。具体操作过程如下:

  1. 先计算出群体中所有个体的适应度的总和
    Σ f ( a i ) , i = 1 , 2 , 3 , 4 Σf(ai) ,i=1,2,3,4 Σf(ai)i=1,2,3,4

  2. 接下来,我们计算每个个体的相对适应度
    p ( a i ) = f ( a i ) / Σ f ( a i ) , i = 1 , 2 , 3 , 4 p(ai)=f(ai)/Σf(ai) ,i=1,2,3,4 p(ai)=f(ai)f(ai)i=1,2,3,4
    这就是每个个体被遗传到下一代种群的概率。

  3. 每个概率值组成一个区间,全部概率值之和为1;

  4. 最后,我们再生成一个0到1之间的随机数,根据该随机数落在哪个概率区间内来确定哪个个体被选中。在这个例子中,

Σ f ( a i ) = 30 Σf(ai)=30 Σf(ai)=30

p(a1)=30.00 %, p(a2)=26.66%,p(a3)=33.33%,p(a4)=10.00%

因此a1区间=[0,0.3],a2区间=[0.3,0.5666],a3区间=[0.5666,0.9],a4区间=[0.9,1]。经过4次选择,a3被选1次,a2被选2次,a4落选:

b1 = [1, 0, 1, 0], totalSize = 6, totalValue = 8, fitValue = 8
b2 = [1, 0, 0, 1], totalSize = 7, totalValue = 10, fitValue = 10
b3 = [1, 0, 1, 0], totalSize = 6, totalValue = 8, fitValue = 8
b4 = [1, 1, 1, 1], totalSize = 14, totalValue = 19, fitValue = 9

5交叉:

  • 本例采用单点交叉的方法,其具体操作过程如下:
    • 首先,将种群中的个体随机配对;
    • 其次,为每对个体随机设定一个交叉点位置;
    • 最后,让每对个体在交叉点处相互交换部分基因。 在这个例子中, b1与b4在第3位后交叉,生成:

c1 = [1, 0, 1, 1], totalSize = 11, totalValue = 15, fitValue = 11
c2 = [1, 1, 1, 0], totalSize = 9, totalValue = 12, fitValue = 12

b2与b3在第2位后交叉,生成:

c3 = [1, 0, 1, 0], totalSize = 6, totalValue = 8, fitValue = 8
c4 = [1, 0, 0, 1], totalSize = 7, totalValue = 10, fitValue = 10

6 突变:

我们采用基本位变异的方法来进行变异运算,其具体操作过程如下:

  • 首先,随机确定每个个体的一个变异位位置;
  • 然后,按照一定的概率将变异位上的基因值取反。 在这个例子中, c1的第1位发生变异:

c1 = [0, 0, 1, 1], totalSize = 9, totalValue = 12, fitValue = 12
c2 = [1, 1, 1, 0], totalSize = 9, totalValue = 12, fitValue = 12
c3 = [1, 0, 1, 0], totalSize = 6, totalValue = 8, fitValue = 8
c4 = [1, 0, 0, 1], totalSize = 7, totalValue = 10, fitValue = 10
————————————————
至此,我们已经找到了2个最优解c1与c2

代码如下

package work3;

import java.util.ArrayList;
import java.util.Collections;

//工具类
public class FitnessTool {

    /**
     * 计算背包总重
     *
     * @param gene01 染色体基因01
     * @param w      物品重
     * @param count  基因数/物品数
     * @return
     */
    public static int totalSize(int[] gene01, int[] w, int count) {
        int total = 0;
        for (int i = 0; i < count; i++) {
            total += (gene01[i] * w[i]);
        }
        return total;
    }

    /**
     * 计算种群中个体适应度
     *
     * @param C         背包容量
     * @param gene01    染色体基因01
     * @param v         物品价值
     * @param totalSize 背包总重
     * @param count     基因数
     * @return
     */
    public static int fitness(int C, int[] gene01, int[] v, int totalSize, int count) {
        int alpha = 2;
        int fitness = 0;
        for (int i = 0; i < count; i++) {
            fitness += (gene01[i] * v[i]);
        }
        if (totalSize <= C) {
            return fitness;
        } else {
            return fitness - alpha * (totalSize - C);
        }
    }


    /**
     * 每个个体被遗传到下一代群体中的概率
     *
     * @param fitnessInt 所有个体的适应度数组
     * @param amount     种群数
     * @return
     */
    public static double[] geneticProbability(int[] fitnessInt, int amount) {
        int totalFitness = 0;
        double[] probability = new double[amount];
        for (int i = 0; i < amount; i++) {
            totalFitness += fitnessInt[i];
        }
        for (int i = 0; i < amount; i++) {
            probability[i] = (double) fitnessInt[i] / totalFitness;
        }
        return probability;
    }

    /**
     * 依据该随机数出现在上述哪一个概率区域内来确定各个个体被选中的次数
     *
     * @param probability 每个个体被遗传到下一代群体中的概率
     * @param amount      种群数
     * @return 各个个体被选中的次数数组
     */
    public static int[] numberOfSelections(double[] probability, int amount) {
        int[] number = new int[amount];

        for (int i = 0; i < amount; i++) {
            double random = Math.random();

            if (probability[0] > random) {
                number[0]++;
            }
            if (probability[0] + probability[1] > random && probability[0] < random) {
                number[1]++;
            }
            if (probability[0] + probability[1] + probability[2] > random && probability[0] + probability[1] < random) {
                number[2]++;
            }

            if (probability[1] + probability[2] + probability[0] < random) {
                number[3]++;
            }
        }
        return number;
    }

    /**
     * 经过选择过后,得到新的染色体组
     *
     * @param popur              旧的染色体组
     * @param numberOfSelections 各个个体被选中的次数数组
     * @return 新的染色体组
     */
    public static int[][] newPopur(int[][] popur, int[] numberOfSelections) {
        int number;
        int first = 0;
        int[][] newPopur = new int[popur.length][popur[0].length];
        for (int i = 0; i < popur.length; i++) {
            number = numberOfSelections[i];

            if (number == 4) {
                for (int j = 0; j < popur[0].length; j++) {
                    newPopur[first][j] = popur[i][j];
                }
                first++;
            }
            if (number >= 3) {
                for (int j = 0; j < popur[0].length; j++) {
                    newPopur[first][j] = popur[i][j];
                }
                first++;
            }
            if (number >= 2) {
                for (int j = 0; j < popur[0].length; j++) {
                    newPopur[first][j] = popur[i][j];
                }
                first++;
            }
            if (number >= 1) {
                for (int j = 0; j < popur[0].length; j++) {
                    newPopur[first][j] = popur[i][j];
                }
                first++;
            }

        }
        return newPopur;
    }

    /**
     * 显示种群的染色体组
     */
    public static void readPopur(int[][] popur) {
        for (int i = 0; i < popur.length; i++) {
            for (int j = 0; j < popur[i].length; j++) {
                System.out.print(popur[i][j] + "\t");
            }
            System.out.println();
        }
    }

    /**
     * 显示1个一维数组
     *
     * @param OneArray
     */
    public static void readOneArray(int[] OneArray) {
        for (int i :
                OneArray) {
            System.out.print(i + "\t");
        }
    }

    /**
     * 单点交叉的方法
     *
     * @param popur 交叉前染色体组
     * @return 交叉后染色体组
     */
    public static int[][] crossPopur(int[][] popur) {
        int[] crossNumber = new int[popur.length];
        int[][] newPopur = new int[popur.length][popur[0].length];//一定要new,不然就是多一个命名,但是是同一个数组

        //使染色体组乱序
        //创建从小到大的数组
        ArrayList list = new ArrayList(popur.length);
        for (int i = 0; i < popur.length; i++) {
            list.add(i);
        }
        //使数组乱序
        Collections.shuffle(list);
        //使染色体组乱序
        for (int i = 0; i < popur.length; i++) {
            crossNumber[i] = (int) list.get(i);
            for (int j = 0; j < popur[0].length; j++) {
                newPopur[i][j] = popur[crossNumber[i]][j];
            }
        }

        //确定交叉点
        int[] temp = new int[popur[0].length];
        for (int i = 0; i < popur.length; i += 2) {
            int intersection = (int) (Math.random() * popur[0].length);

            for (int j = intersection; j < popur[0].length; j++) {
                temp[j] = newPopur[i][j];
                newPopur[i][j] = newPopur[i + 1][j];
                newPopur[i + 1][j] = temp[j];
            }
        }
        return newPopur;
    }

    /**
     * 对染色体组采用基本位变异的方法来进行变异
     *
     * @param popur 染色体组
     * @return 变异后的染色体组
     */
    public static int[][] variation(int[][] popur) {

        int[] varPupor = new int[popur.length];//变异的个体

        for (int i = 0; i < popur.length; i++) {
            varPupor[i] = (int) (Math.random() + 0.2);
        }

        int varPosition = (int) (Math.random() * popur[0].length);//变异的位置
        for (int i = 0; i < popur.length; i++) {
            if (varPupor[i] == 1) {
                popur[i][varPosition] = popur[i][varPosition] > 0 ? 0 : 1;
            }
        }
        return popur;
    }
}
===========
package work3;

//物品类
public class Goods {

    int[] w = {2, 3, 4, 5};//物品重量
    int[] v = {3, 4, 5, 7};//物品价值

    public Goods() {

    }


    public int[] getW() {
        return w;
    }

    public void setW(int[] w) {
        this.w = w;
    }

    public int[] getV() {
        return v;
    }

    public void setV(int[] v) {
        this.v = v;
    }
}
===========
    package work3;
//染色体类
public class Chromosome {

    private int count;//物品数量

    public Chromosome() {
    }

    public Chromosome(int count) {
        this.count = count;
    }

    /**
     * 一个染色体上的基因显示
     *
     * @return
     */
    public int[] getGene01() {
        int[] x = new int[count];
        for (int i = 0; i < count; i++) {
            x[i] = (int) (Math.random() + 0.5);
        }
        return x;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}
==========
    package work3;

//背包类
public class Bag00 {
    int C = 9;//背包容量
    int amount;//种群数量
    int[][] popur;//染色体组01
    Chromosome chromosome;//染色体

    public Bag00(int amount, int count) {
        this.amount = amount;
        chromosome = new Chromosome(count);
        getPopur();

    }


    /**
     * 生成种群的染色体组
     *
     * @return 种群的染色体组01
     */
    public int[][] getPopur() {
        int[] gene01;
        popur = new int[this.amount][chromosome.getCount()];
        for (int i = 0; i < amount; i++) {
            for (int j = 0; j < chromosome.getCount(); j++) {
                gene01 = chromosome.getGene01();
                popur[i][j] = gene01[j];
            }
        }
        return popur;
    }

    /**
     * 显示种群的染色体组
     */
    public void readPopur() {
        for (int i = 0; i < amount; i++) {
            for (int j = 0; j < chromosome.getCount(); j++) {
                System.out.print(popur[i][j] + "\t");
            }
            System.out.println();
        }
    }
}

======
    package work3;

//背包装物品
public class BagSet {

    Bag00 bag00;
    Goods goods;

    public BagSet() {
        bag00 = new Bag00(4, 4);
        System.out.println("\n初始种群\n");
        bag00.readPopur();//初始种群
        System.out.println("========");
        goods = new Goods();
    }

    /**
     * 经过一次遗传得到的染色体组
     *
     * @return
     */
    public int[][] flowPath() {
        int[] totalSize = new int[bag00.amount];
        int[] fitness = new int[bag00.amount];
        //计算总重与适应度
        for (int i = 0; i < bag00.amount; i++) {
            totalSize[i] = FitnessTool.totalSize(bag00.popur[i], goods.w, bag00.chromosome.getCount());
            fitness[i] = FitnessTool.fitness(bag00.C, bag00.popur[i], goods.v, totalSize[i], bag00.chromosome.getCount());
        }
        System.out.println("\n总重{2,3,4,5}\n");
        FitnessTool.readOneArray(totalSize);
        System.out.println("\n适应度{3,4,5,7}\n");
        FitnessTool.readOneArray(fitness);
        //每个个体被遗传到下一代群体中的概率
        double[] geneticProbability = FitnessTool.geneticProbability(fitness, bag00.amount);
        //个体被选中的次数
        System.out.println("\n个体被选中的次数\n");
        int[] numberOfSelections = FitnessTool.numberOfSelections(geneticProbability, bag00.amount);
        FitnessTool.readOneArray(numberOfSelections);

        System.out.println("\n经过选择过后,得到新的染色体组\n");

        int[][] newPopur = FitnessTool.newPopur(bag00.popur, numberOfSelections);
        FitnessTool.readPopur(newPopur);

        System.out.println("\n交叉后染色体组\n");
        int[][] crossPopur = FitnessTool.crossPopur(newPopur);
        FitnessTool.readPopur(crossPopur);

        System.out.println("\n变异后的染色体组\n");
        int[][] variation = FitnessTool.variation(newPopur);
        FitnessTool.readPopur(variation);
        return variation;

    }

    /**
     * 经过500次遗传得到的染色体组
     *
     * @return
     */
    public int[][] flowPath50() {
        int[] totalSize = new int[bag00.amount];
        int[] fitness = new int[bag00.amount];
        int[][] variation = new int[bag00.amount][bag00.chromosome.getCount()];
        ;
        for (int i = 0; i < 500; i++) {
            //计算总重与适应度
            for (int j = 0; j < bag00.amount; j++) {
                totalSize[j] = FitnessTool.totalSize(bag00.popur[j], goods.w, bag00.chromosome.getCount());
                fitness[j] = FitnessTool.fitness(bag00.C, bag00.popur[j], goods.v, totalSize[j], bag00.chromosome.getCount());
            }

            //每个个体被遗传到下一代群体中的概率
            double[] geneticProbability = FitnessTool.geneticProbability(fitness, bag00.amount);
            //个体被选中的次数

            int[] numberOfSelections = FitnessTool.numberOfSelections(geneticProbability, bag00.amount);

            //经过选择过后,得到新的染色体组
            int[][] newPopur = FitnessTool.newPopur(bag00.popur, numberOfSelections);

            //交叉后染色体组
            int[][] crossPopur = FitnessTool.crossPopur(newPopur);

            //变异后的染色体组
            variation = FitnessTool.variation(crossPopur);
        }
        return variation;

    }

    /**
     * 计算染色体组的总重与适应度
     *
     * @param popur
     */
    public void calaFitness(int[][] popur) {
        int[] totalSize = new int[bag00.amount];
        int[] fitness = new int[bag00.amount];
        //计算总重与适应度
        for (int i = 0; i < bag00.amount; i++) {
            totalSize[i] = FitnessTool.totalSize(popur[i], goods.w, bag00.chromosome.getCount());
            fitness[i] = FitnessTool.fitness(bag00.C, popur[i], goods.v, totalSize[i], bag00.chromosome.getCount());
        }
        System.out.println("\n总重{2,3,4,5}\n");
        FitnessTool.readOneArray(totalSize);
        System.out.println("\n适应度{3,4,5,7}\n");
        FitnessTool.readOneArray(fitness);
    }
}
====
    package work3;

//主函数启动

public class TestBags1 {
    public static void main(String[] args) {
        BagSet bagSet = new BagSet();
        int[][] flowPath = bagSet.flowPath50();
        System.out.println();
        System.out.println("========");
        FitnessTool.readPopur(flowPath);//显示最终的染色体组
        bagSet.calaFitness(flowPath);//显示最终的染色体组的适应度
    }
}

显示效果

第一次运行

初始种群

1	1	1	0	
0	0	1	0	
1	0	0	0	
0	0	0	0	
========

最终的染色体组

0	0	1	1	
1	1	1	0	
0	0	1	0	
1	0	0	0	

总重{2,3,4,5}

9	9	4	2	
适应度{3,4,5,7}

12	12	5	3	

第二次运行

初始种群

1	0	0	1	
1	1	0	0	
1	1	1	0	
1	0	1	1	
========

最终的染色体组

1	0	1	0	
1	1	0	1	
1	0	1	1	
1	0	1	1	

总重{2,3,4,5}

6	10	11	11	
适应度{3,4,5,7}

8	12	11	11	
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值