基于贪心策略的混合遗传算法求解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