用贪心算法解决0-1背包问题是算法界较为经典的一个问题,笔者尝试用一个python脚本,实现对输入的问题数据生成相应的最优结果。
贪心算法(greedy algorithm),又称贪婪法,是寻找最优解问题的常用方法。这种方法一般将求解过程分成若干个步骤在每个步骤都应用贪心原则,选取当前状态下最好的或最优的选择(局部最有利的选择),并以此希望最后堆叠出的结果也是最好或最优的解。贪婪法的每次决策都以当前情况为基础并根据某个最优原则进行选择,不从整体上考虑其他各种可能的情况。
贪婪法和动态规划法以及分治法一样,都需要对问题进行分解,定义最优解的字结构,但是贪婪法与其他方法最大的不同在于,贪婪法每一步选择完之后,局部最优解就确定了,不再进行回溯处理,直到算法结束。因此,贪婪法只有在很少的情况下可以得到真正的最优解,比如最短路径问题、图的最小生成树问题。大多数情况下,由于选择策略的“短视”,贪婪法会错过真正的最优解,得不到问题的真正答案。但是贪婪法简单高效,省去了为找最优解可能需要的穷举操作,可以得到与最优解比较接近的近似最优解,通常作为其他算法的辅助算法使用。
贪婪法的基本设计思想有以下三个步骤:
(1)建立对问题精确描述的数学模型,包括定义最优解的模型
(2)将问题分解为一系列子问题,同时定义子问题的最优解结构。
(3)应用贪心原则确定每个子问题的局部最优解,并根据最优解的模型,用子问题的局部最优解堆叠出全局最优解。
定义最优解的模型通常和定义子问题的最优解结构是同时进行的,最优解的模型一般都体现了最优解子问题的分解结构和堆叠方式。对于子问题的分解有多种方式,有的问题可以按照问题的求解过程一步一步地进行分解,每一步都在前一步的基础上选择当前最好的解,每做一次选择就将问题简化为一个规模更小的问题,当最后一步的求解完成后就得到了全局最优解。还有的问题可以将问题分解成相对独立的几个子问题,对每个子问题求解完成后再按照一定的规则将其组合起来得到全局最优解。
有N件物品和一个承重为C的背包,每件物品重量是wi, 价值是pi, 求解将哪几件物品装入背包可使这些物品的重量总和不超过C的情况下价值总和最大。
背包问题(knapsack problem)是此类组合优化的NP问题的统称,比如货箱装载问题、货船载物问题等,因问题最初来源于如何选择最合适的物品装在背包中而得名。这个问题隐含了一个条件,每个物品只有一件,也就是限定每件物品只能选择0个或1个,因此又被称为0-1背包问题。
在这个问题中,常见的贪婪策略有三种。第一种是根据物品的重量选择,每次都选重量最低的物品。第二种是根据物品的价值选择,每次都选价值最高的物品。第三种是定义一个价值密度的概念,每次都选择重量最轻的物品,将价值密度si定义为pi/wi。
笔者在这个python脚本文件中将三种策略都封装进函数,取得各自的结果后,再比较所得总价值,最终输出价值最高的一种结果。
笔者利用几个封装的函数,在主程序中分别调用后得出结果。
根据输入选择是否使用默认的一组数据进行演示,把重量、价值和总重量分别存入weight、price和C,并打印出来以供核对。
def Initial():
'''确定物品重量、价值和背包总重量'''
option = input('是否选择使用默认数据(Y/N): ')
if option == 'Y':
weight = [35, 30, 60, 50, 40, 10, 25]
price = [10, 40, 30, 50, 35, 40, 30]
C = 150
else:
weight = list(map(int, input('请输入物品重量,用空格分开:').split( )))
price = list(map(int, input('请输入相应的物品价值,用空格分开: ').split( )))
C = int(input('请输入背包总重量限制: '))
item = list(zip(weight,price))
print('重量,价值:' + item.__str__() + '\n总重量限制:' + C.__str__())
return item, C
分别用三个函数实现了三种选择策略,函数最终返回的结果是按照相应选择方法对物品排序后的索引值,以供算法函数使用。
def Weight(item):
'''选重量最小的物品'''
data = np.array(item)
idex = np.lexsort([-1*data[:,1], data[:,0]])
return idex
def Price(item):
'''选价值最大的物品'''
data = np.array(item)
idex = np.lexsort([data[:,0], -1*data[:,1]])
return idex
def Density(item):
'''选价值密度最大的物品'''
number = len(item)
data = np.array(item)
data_list = [0] * number
for i in range(number):
data_list[i] = (data[i,1])/(data[i,0])
data_set = np.array(data_list)
idex = np.argsort(-1*data_set)
return idex
贪心算法函数实现了具体的问题解决过程,用初始化的数据和索引值作为参数,计算后返回一组最优化选择的结果。
def GreedyAlgo(item, C, idex):
'''贪心算法'''
number = len(item)
status = [0] * number
total_weight = 0
total_value = 0
for i in range(number):
if item[idex[i],0] <= C:
total_weight += item[idex[i],0]
total_value += item[idex[i],1]
status[idex[i]] = 1
C -= item[idex[i],0]
else:
continue
return total_weight, total_value, status
比较用三种策略计算后得到结果中总价值的大小,并返回价值最高的一组结果。
def Compare(total_value1, total_value2, total_value3):
'''比较三种结果'''
values = zip(total_value1, total_value2, total_value3)
data = np.array([total_value1[1], total_value2[1], total_value3[1]])
idex = np.argsort(data)
value = list(zip(*values))
results = list(value[idex[2]])
return results
主函数通过调用以上几个函数,最终实现问题的解决方案,并打印出最优结果,同时打印出三种策略各自的最优结果,以供检查。
def main():
'''主体结构'''
item0, C = Initial()
item = np.array(item0)
idex_weight = Weight(item)
idex_price = Price(item)
idex_Density = Density(item)
results_weight = GreedyAlgo(item, C, idex_weight)
print(results_weight)
results_Price = GreedyAlgo(item, C, idex_price)
print(results_Price)
results_Density = GreedyAlgo(item, C, idex_Density)
print(results_Density)
results = Compare(results_weight, results_Price, results_Density)
print(results)
python脚本文件中,除了上述几个函数,还有两行代码以以实现运行。
import numpy as np
main()
以上,代码就全部编写完成了,下面来看看脚本文件在fish中运行的结果。
如图,fish中显示了使用默认数据集和手动输入的数据集运行的两种结果。
笔者第一次发博,代码和算法都很生疏,还存在很多问题,望广大网友多多批评指正,共同进步!
整个过程从社区中学习到了很多,感谢广大网友。
主要参考内容:
[1]《算法的乐趣》——王晓华
[2]https://blog.csdn.net/sunny1235435/article/details/95603969.