基于贪心策略的混合遗传算法求解01背包问题

基于贪心策略的混合遗传算法求解01背包问题

摘要:本文探讨了使用基于贪心策略的混合遗传算法求解01背包问题的方法。首先介绍了01背包问题的定义及其NP难特性,分析了传统贪心算法和动态规划方法的优缺点。然后详细阐述了遗传算法的基本原理及其在求解01背包问题中的应用,包括个体编码、适应度函数设计、选择、交叉和变异等操作。重点提出了一种结合贪心策略的混合遗传算法,通过在遗传操作中引入贪心算子来优化解的质量和收敛速度。实验结果表明,该混合算法相比传统遗传算法能获得更接近最优解的结果,在处理大规模01背包问题时表现出更好的性能。文章提供了完整的Python实现代码,并通过对比实验验证了算法的有效性。

01背包问题

01背包问题是著名的 NPNPNP 难问题。

其要求解的问题即有n个物品,它们有各自的体积和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?

解决01背包问题最经典的解法便是贪心和动态规划。

若采用贪心策略求解01背包问题,其时间复杂度为O(nlogn),时间复杂度较低,但是贪心求解时难以得到整体最优解,有时所得的解与最优解相差甚远。

若采用动态规划求解01背包问题,则必然能够得到最优解,但是动态规划求解01背包问题的时间复杂度为O(背包容量 * n),因此对于大规模的01背包问题便存在计算量大、迭代时间长的弱点。

遗传算法克服了传统优化方法的缺点,模拟生物进化过程,采用种群搜索和随机搜索机制,是一种多路径的全局优化方法。

遗传算法作为一种启发式算法,求解01背包问题,并不能保证得到最优解,然而经过贪心策略改进的遗传算法能够很快收敛到最优解,并且得到解也比普通遗传算法更接近最优解。

动态规划求解01背包问题(python代码)

python 复制代码
goods = []

f = open("test", "r")  # 打开测试数据文件,读取数据
line = f.readline()
line = line[:-1]
Backpack_volume, n = int(line.split()[0]), int(line.split()[1]) # 背包容量以及物品数
print(Backpack_volume, n)
while line:  # 直到读取完文件
     line = f.readline()  # 读取一行文件,包括换行符
     line = line[:-1]  # 去掉换行符,也可以不去
     goods.append([int(line.split()[i]) for i in range(len(line.split()))])
goods = goods[:-1]  # 去掉换行

# print(self.goods,len(self.goods))
f.close()  # 关闭文件

# print(goods)

dp = [[0 for _ in range(Backpack_volume + 1)] for _ in range(n + 1)]

for i in range(1, n + 1):
    for j in range(1, Backpack_volume + 1):
        if goods[i - 1][0] <= j:
            dp[i][j] = max(goods[i - 1][1] + dp[i - 1][j - goods[i - 1][0]], dp[i - 1][j])
        else:
            dp[i][j] = dp[i - 1][j]

# print(dp)
print(dp[n][Backpack_volume])

遗传算法求解01背包问题

python 复制代码
# -*- coding: utf-8 -*-
"""
遗传算法
"""
import random

SCORE_NONE = -1.0

class Life(object):
      """个体类"""
      def __init__(self, aGene = None):
            self.gene = aGene
            self.score = SCORE_NONE

class GA(object):
    """遗传算法类"""

    def __init__(self, aCrossRate, aMutationRate, aLifeCount, aGeneLength, aMatchFun=lambda life: 1):
        self.crossRate = aCrossRate               # 交叉概率
        self.mutationRate = aMutationRate         # 变异率
        self.lifeCount = aLifeCount               # 种群大小
        self.geneLength = aGeneLength             # 染色体长度(物品个数)
        self.matchFun = aMatchFun                 # 适应度函数
        self.lives = []                           # 种群
        self.best = None                          # 保存这一代中最好的个体
        self.generation = 1                       # 迭代次数
        self.crossCount = 0                       # 交叉个数
        self.mutationCount = 0                    # 变异个数
        self.bounds = 0.0                         # 适应度值之和,用于选择时计算概率,公式:Fi/sum(Fi).其中Fi为个体i对应的适应度值

        self.initPopulation()

    def initPopulation(self):
        """初始化种群"""
        self.lives = []
        for i in range(self.lifeCount):
            gene = [random.randint(0,1) for x in range(self.geneLenght)] # 初始化染色体
            # shuffle() 方法将序列的所有元素随机排序。
            random.shuffle(gene)
            life = Life(gene) # 生成个体
            self.lives.append(life)

    def judge(self):
        """评估,计算每一个个体的适配值"""
        self.bounds = 0.0
        self.best = self.lives[0]
        for life in self.lives:
            life.score = self.matchFun(life) # 获得个体的适应值
            self.bounds += life.score
            if self.best.score < life.score:
                self.best = life # 选择最优个体

    def cross(self, parent1, parent2):
        """
        交叉
        随机从其他染色体上截取一段基因,与原染色体上的此段基因片段交换,来得到新染色体
        """
        index1 = random.randint(0, self.geneLenght - 1)
        index2 = random.randint(index1, self.geneLenght - 1)
        newGene = []

        newGene.extend(parent1.gene[:index1])
        newGene.extend(parent2.gene[index1:index2])
        newGene.extend(parent1.gene[index2:])

        self.crossCount += 1
        return newGene      # 返回交叉后的parent1.gene

    def mutation(self, gene):
        """变异算子,突变,两个基因互换位置"""
        index1 = random.randint(0, self.geneLenght - 1)

        newGene = gene[:]       # 产生一个新的基因序列,以免变异的时候影响父种群
        newGene[index1] = (newGene[index1]+1)%2
        if random.random() < self.mutationRate:
            self.mutation(newGene)
        self.mutationCount += 1
        return newGene

    def getOne(self):
        """选择一个个体,适应值越大,则个体被选中的概率越大"""
        r = random.uniform(0, self.bounds)
        for life in self.lives:
            r -= life.score
            if r <= 0:
                return life

        raise Exception("选择错误", self.bounds)

    def newChild(self):
        """产生新后代"""
        parent1 = self.getOne()
        rate = random.random()      # 0~1的随机数

        # 按概率交叉
        if rate < self.croessRate:
            # 交叉
            parent2 = self.getOne()
            gene = self.cross(parent1, parent2)
        else:
            gene = parent1.gene

        # 按概率突变
        rate = random.random()
        if rate < self.mutationRate:
            gene = self.mutation(gene)

        return Life(gene)

    def next(self):
        """产生下一代"""
        self.judge()
        newLives = []
        newLives.append(self.best)      # 把最好的个体加入下一代
        while len(newLives) < self.lifeCount:
            newLives.append(self.newChild())
        self.lives = newLives       #更新种群
        self.generation += 1        #更新迭代次数
python 复制代码
"""
遗传算法求解01背包问题
"""
import random
import math
from GA import GA


class KP(object): #01背包类
    def __init__(self, aLifeCount=200):
        # 初始化
        self.init()
        self.lifeCount = aLifeCount
        self.ga = GA(aCrossRate=0.75,  # 交叉概率
                     aMutationRage=0.05,  # 突变率
                     aLifeCount=self.lifeCount,  # 种群大小
                     aGeneLenght=len(self.goods),  # 染色体长度(城市个数),其中基因为城市排列顺序。
                     aMatchFun=self.matchFun())  # 适值函数

    def init(self):
        self.goods = []
        f = open("test", "r")  # 设置文件对象
        line = f.readline()
        line = line[:-1]
        self.Volume = float(line.split()[0]) # 背包容量
        # print(self.allGoods)
        while line:  # 直到读取完文件
            line = f.readline()  # 读取一行文件,包括换行符
            line = line[:-1]  # 去掉换行符,也可以不去
            self.goods.append([float(line.split()[i]) for i in range(len(line.split()))])
        self.goods = self.goods[:-1]  # 去掉换行
        # print(self.goods,len(self.goods))
        f.close()  # 关闭文件

    def price(self, order):
        # order中存放着选择
        price = [0.0, 0.0]
        for i in range(0, len(self.goods)):
            price[0] += self.goods[i][0] * order[i]  # 重量
            price[1] += self.goods[i][1] * order[i]  # 价值

        return price

    def matchFun(self):
        """适值函数"""
        Max = max(self.Volume, abs(sum(self.goods[i][0] for i in range(len(self.goods))) - self.Volume))

        return lambda life: self.price(life.gene)[1] / (self.price(life.gene)[0] + 1 - self.Volume) * \
                            (1 - abs(self.price(life.gene)[0] - self.Volume) / Max) \
            if self.price(life.gene)[0] > self.Volume \
            else self.price(life.gene)[1] * (1 - abs(self.price(life.gene)[0] - self.Volume) / Max)

    # n为迭代次数
    def run(self, n=0):
        price = [0, 0]
        while n > 0:
            self.ga.next()
            price = self.price(self.ga.best.gene)
            n -= 1
            print("generation:", self.ga.generation, "  weights :", price[0], "----", "price :", price[1])
        print(self.ga.best.gene)
        return price


def main():
    '''多次运算,取出现次数最多的结果作为最终值'''
    res = []
    for i in range(1):
        tsp = KP()
        res.append(tsp.run(200))
    print(res)
    print(max(res, key=res.count))


if __name__ == '__main__':
    main()

基于贪婪的混合遗传算法求解01背包问题

下图展示了基于贪婪策略的混合遗传算法整体流程:
贪婪算子优化流程


交叉
不交叉
变异
不变异


开始
初始化种群
计算价值密度并排序
评估个体适应度
是否满足终止条件?
输出最优解
选择操作
按概率执行
交叉操作
直接复制
贪婪算子优化
按概率执行
变异操作
保持原基因
贪婪算子优化
生成新种群
输入: 个体基因
计算当前总重量
总重量 < 背包容量?
按价值密度降序添加物品
按价值密度升序移除物品
输出优化后基因

python 复制代码
# -*- coding: utf-8 -*-
"""
遗传算法
利用贪心算法的思想对传统遗传算法的繁殖算子进行改进
"""
import random

SCORE_NONE = -1.0

class Life(object):
      """个体类"""
      def __init__(self, aGene = None):
            self.gene = aGene
            self.score = SCORE_NONE

class Hybrid_GA_based_greed(object):
    """遗传算法类"""

    def __init__(self, aCrossRate, aMutationRage, aLifeCount, aGeneLenght, aMatchFun=lambda life: 1):
        self.croessRate = aCrossRate              # 交叉概率
        self.mutationRate = aMutationRage         # 突变率
        self.lifeCount = aLifeCount               # 种群大小
        self.geneLenght = aGeneLenght             # 染色体长度
        self.matchFun = aMatchFun                 # 适值函数
        self.lives = []                           # 种群
        self.best = None                          # 保存这一代中最好的个体
        self.generation = 1                       # 迭代次数
        self.crossCount = 0                       # 交叉个数
        self.mutationCount = 0                    # 变异个数
        self.bounds = 0.0                         # 适配值之和,用于选择时计算概率,公式:Fi/sum(Fi).其中Fi为个体i对应的适应值
        self.value_density = []                   # 价值密度
        self.init()

        self.initPopulation()

    def init(self):
        self.goods = []
        f = open("test", "r")  # 设置文件对象
        line = f.readline()
        line = line[:-1]
        self.Volume = float(line.split()[0])  # 背包容量
        # print(self.allGoods)
        while line:  # 直到读取完文件
            line = f.readline()  # 读取一行文件,包括换行符
            line = line[:-1]  # 去掉换行符,也可以不去
            self.goods.append([float(line.split()[i]) for i in range(len(line.split()))])
        self.goods = self.goods[:-1]  # 去掉换行
        # print(self.goods,len(self.goods))
        n = len(self.goods)
        self.value_density = [[self.goods[i][1] / self.goods[i][0], i] for i in range(n)]
        self.value_density.sort(key=lambda x: x[0], reverse=True)
        # print(self.value_density)
        f.close()  # 关闭文件

    def initPopulation(self):
        """初始化种群"""
        self.lives = []
        for i in range(self.lifeCount):
            gene = [random.randint(0,1) for x in range(self.geneLenght)] # 初始化染色体
            # shuffle() 方法将序列的所有元素随机排序。
            random.shuffle(gene)
            life = Life(gene) # 生成个体
            self.lives.append(life)

    def judge(self):
        """评估,计算每一个个体的适配值"""
        self.bounds = 0.0
        self.best = self.lives[0]
        for life in self.lives:
            # print(life.gene)
            self.greedy(life.gene)
            life.score = self.matchFun(life) # 获得个体的适应值
            self.bounds += life.score
            if self.best.score < life.score:
                self.best = life # 选择最优个体

    def greedy(self, gene):
        """贪婪算子"""
        # print(self.value_density)
        cnt = 0
        for i in range(self.geneLenght):
            cnt += gene[i] * self.goods[i][0]

        if cnt < self.Volume:
            for j in range(self.geneLenght):
                i = self.value_density[j][1]
                if cnt + self.goods[i][0] < self.Volume and gene[i] == 0:
                    cnt += self.goods[i][0]
                    gene[i] = 1
                else:
                    break
        else:
            for j in range(self.geneLenght - 1, -1, -1):
                i = self.value_density[j][1]
                if cnt <= self.Volume:
                    break
                if gene[i] == 1:
                    cnt -= self.goods[i][0]
                    gene[i] = 0


    def cross(self, parent1, parent2):
        """
        交叉
        随机从其他染色体上截取一段基因,与原染色体上的此段基因片段交换,来得到新染色体
        """
        index1 = random.randint(0, self.geneLenght - 1)
        index2 = random.randint(index1, self.geneLenght - 1)
        newGene = []

        newGene.extend(parent1.gene[:index1])
        newGene.extend(parent2.gene[index1:index2])
        newGene.extend(parent1.gene[index2:])

        self.crossCount += 1
        # print(newGene)
        self.greedy(newGene)
        return newGene      # 返回交叉后的parent1.gene

    def mutation(self, gene):
        """变异算子,突变,两个基因互换位置"""
        index1 = random.randint(0, self.geneLenght - 1)
        newGene = gene[:]       # 产生一个新的基因序列,以免变异的时候影响父种群
        newGene[index1] = (newGene[index1] + 1) % 2
        # print(newGene)
        if random.random() < self.mutationRate:
            self.mutation(newGene)
        self.mutationCount += 1
        # print(self.Value_density)
        self.greedy(newGene)
        return newGene

    def getOne(self):
        """选择一个个体,适应值越大,则个体被选中的概率越大,选择算子"""
        r = random.uniform(0, self.bounds)
        for life in self.lives:
            r -= life.score
            if r <= 0:
                return life

        raise Exception("选择错误", self.bounds)

    def newChild(self):
        """产生新后代"""
        parent1 = self.getOne()
        rate = random.random()      # 0~1的随机数

        # 按概率交叉
        if rate < self.croessRate:
            # 交叉
            parent2 = self.getOne()
            gene = self.cross(parent1, parent2)
        else:
            gene = parent1.gene

        # 按概率突变
        rate = random.random()
        if rate < self.mutationRate:
            gene = self.mutation(gene)

        return Life(gene)

    def next(self):
        """产生下一代"""
        self.judge()
        newLives = []
        newLives.append(self.best)      # 把最好的个体加入下一代
        while len(newLives) < self.lifeCount:
            newLives.append(self.newChild())
        self.lives = newLives       #更新种群
        self.generation += 1        #更新迭代次数
python 复制代码
"""
遗传算法求解01背包问题
"""
import random
import math
from Hybrid_GA_based_greed import Hybrid_GA_based_greed


class KP(object): #01背包类
    def __init__(self, aLifeCount=200):
        # 初始化
        self.init()
        self.lifeCount = aLifeCount

        self.ga = Hybrid_GA_based_greed(aCrossRate=0.7,  # 交叉概率
                                        aMutationRage=0.1,  # 突变率
                                        aLifeCount=self.lifeCount,  # 种群大小
                                        aGeneLenght=len(self.goods),  # 染色体长度(物品个数),其中基因为物品排列顺序。
                                        aMatchFun=self.matchFun())   # 适值函数

    def init(self):
        self.goods = []
        f = open("test", "r")  # 设置文件对象
        line = f.readline()
        line = line[:-1]
        self.Volume = float(line.split()[0]) # 背包容量
        # print(self.allGoods)
        while line:  # 直到读取完文件
            line = f.readline()  # 读取一行文件,包括换行符
            line = line[:-1]  # 去掉换行符,也可以不去
            self.goods.append([float(line.split()[i]) for i in range(len(line.split()))])
        self.goods = self.goods[:-1]  # 去掉换行
        # print(self.goods,len(self.goods))
        # print(self.value_density)
        f.close()  # 关闭文件

    def price(self, order):
        # order中存放着选择
        price = [0.0, 0.0]
        for i in range(0, len(self.goods)):
            price[0] += self.goods[i][0] * order[i]  # 重量
            price[1] += self.goods[i][1] * order[i]  # 价值
        return price

    def matchFun(self):
        """适值函数"""
        return lambda life: self.price(life.gene)[1] / sum(self.goods[i][0] for i in range(len(self.goods)))
        # Max = max(self.Volume, abs(sum(self.goods[i][0] for i in range(len(self.goods))) - self.Volume))
        #
        # return lambda life: self.price(life.gene)[1] / (self.price(life.gene)[0] + 1 - self.Volume) * \
        #                     (1 - abs(self.price(life.gene)[0] - self.Volume) / Max) \
        #     if self.price(life.gene)[0] > self.Volume \
        #     else self.price(life.gene)[1] * (1 - abs(self.price(life.gene)[0] - self.Volume) / Max)

    # n为迭代次数
    def run(self, n=0):
        price = [0, 0]
        while n > 0:
            self.ga.next()
            price = self.price(self.ga.best.gene)
            n -= 1
            print("generation:", self.ga.generation, "  weights :", price[0], "----", "price :", price[1])
        print(self.ga.best.gene)
        return price


def main():
    '''多次运算,取众数即为最终值'''
    res = []
    for i in range(1):
        tsp = KP()
        res.append(tsp.run(200))
    print(res)
    print(max(res, key=res.count))


if __name__ == '__main__':
    main()

实验数据对比

下表展示了在不同测试数据上,传统遗传算法(GA)与基于贪婪策略的混合遗传算法(Hybrid GA)与最优解的对比结果。所有数值均为背包内物品的总价值(单位:价值单位)。为更直观地展示算法性能,增加了"GA与最优解差距"和"Hybrid GA与最优解差距"两列,以百分比形式表示算法结果与最优解的接近程度。

数据 最优解(价值) 遗传算法(价值) GA与最优解差距 贪婪混合遗传算法(价值) Hybrid GA与最优解差距
数据1 133 133 0.00% 133 0.00%
数据2 334 334 0.00% 334 0.00%
数据3 383 383 0.00% 383 0.00%
数据4 2614 2172 -16.91% 2612 -0.08%
数据5 2541 2083 -18.02% 2541 0.00%
数据6 2617 2177 -16.81% 2615 -0.08%
数据7 2461 2042 -17.03% 2461 0.00%
数据8 2397 2098 -12.47% 2397 0.00%
数据9 2460 2062 -16.18% 2459 -0.04%
数据10 2852 2343 -17.85% 2852 0.00%

说明

  • GA与最优解差距 = (遗传算法结果 - 最优解) / 最优解 × 100%
  • Hybrid GA与最优解差距 = (贪婪混合遗传算法结果 - 最优解) / 最优解 × 100%
  • 负百分比表示算法结果低于最优解,绝对值越小表示越接近最优解
  • 从数据可以看出,贪婪混合遗传算法在大多数测试数据上都能达到或非常接近最优解(差距在0.08%以内),而传统遗传算法与最优解的差距较大(最大达18.02%),这验证了引入贪心策略能显著提升遗传算法的求解质量。

参考文献

1\]陈桢,钟一文,林娟.求解0-1背包问题的混合贪婪遗传算法\[J\].计算机应用,2021,41(01):87-94. \[2\]严太山.用基于贪婪算法的混合遗传算法求解0/1背包问题\[J\].现代计算机(专业版),2007(08):14-17. \[3\]郝斌斌,孙玮玮,李康.基于改进贪婪策略遗传算法0/1背包问题求解\[J\].交通科技与经济,2015,17(01):1-4

相关推荐
才兄说1 小时前
机器人二次开发机器人动作定制?动作迁移数据优化
python
洛水水2 小时前
【力扣100题】53.最长回文子串
算法·leetcode·职场和发展
jieyucx2 小时前
Go 语言 sort 包详解:从基础排序到自定义排序(含底层原理+零基础看懂)
算法·golang·排序算法·sort
用户8356290780512 小时前
用 Python 实现 Excel 散点图绘制与定制
后端·python
PAK向日葵2 小时前
从零实现 Python 虚拟机(一):PVM 基本原理介绍
python
神所夸赞的夏天2 小时前
创建虚拟环境提示SSLError错误
python
极光代码工作室2 小时前
基于机器学习的二手商品价格预测系统
人工智能·python·深度学习·机器学习
无情的西瓜皮2 小时前
MCP协议实战:从零搭建一个AI Agent工具服务器
运维·服务器·python
叁散3 小时前
ESP32 LCD1602显示实验报告
算法