python创建树结点——分支限界法解决0-1背包问题
介绍
python构建结点时注意灵活使用class类,可以灵活使用树的结构体,也可以灵活在一个类里面封装多个函数方法。以下是定义树结点的一个方法——之前没试过python定义树节点。然后创建新节点的时候可以直接用flag代表是上一个结点的左/右结点,father代表父节点。
示例
如下所示
class node: #定义树结点
def __init__(self, father, flag,……):
self.father = father#该结点的父节点
self.flag = flag#代表左节点或右节点——本题是指取物品或不取物品
self.……
实战
下面附带做练习题分支限界法的python代码(参考了别人的)但好像别分支限界代码有点小问题但问题不大改改就好。
参考文章:
四种方法解0-1背包问题-基于python
补充——分支限界法算法思想
(1)首先,要对输入数据进行预处理,将各物品依其单位重量价值从大到小进行排列。
(2)在下面优先队列分支限界法中,节点的优先级由已装袋的物品价值加上剩下的最大单位重量价值的物品装满剩余容量的价值和。
每次选择扩展结点都选择最大预估价值和。这些待选择的队列结点都是所谓的“活结点”(当然本题示例代码是顺序队列,也即按照依次进入队列先后顺序,来逐渐把队列结点消耗完)消耗:即指队列中结点已经扩展完。
这一部分由bound函数计算价值上界。注意剩下的物品计算价值是可以首先单个物品全部装入,再单个物品部分装入。
(3)算法首先检查当前扩展结点的左儿子结点的可行性。如果该左儿子结点是可行结点,即满足约束条件,则将它加入到子集树和活结点优先队列中。当前扩展结点的右儿子结点一定是可行结点(可以满足约束条件),仅当右儿子结点满足上界限界函数时才将它加入子集树和活结点优先队列。当扩展到叶节点时为问题的最优值。
#分支限界法
import numpy as np
class branchbound:
def __init__(self, w, v, c, cw, cp, bestx):#初始化赋值
self.w = np.array(w)
self.v = np.array(v)
self.c = c
self.cw = cw
self.cp = cp
self.bestx = bestx
def value_sort(self):
#求单位质量大小 并降序排列
per = self.v / self.w#得到单位价值矩阵
sorted = np.sort(per)#排列单位价值矩阵
index = np.argsort(per)#argsort()函数是将x中的元素从小到大排列,提取其对应的index(索引)
list = []
for i in sorted:#储存单位价值的具体值
list.append(i)
list.reverse()#逆序转成降序排列
list1 = []
for i in index:#储存对应的索引
list1.append(i)
list1.reverse()#降序排列
index = np.array(list1)
#以下为排序后的拷贝修改处理
a = self.v.copy()
b = self.w.copy()
for i in range(self.v.size):
a[i] = self.v[index[i]]
b[i] = self.w[index[i]]
self.v = a.copy()#修改排列后的物品价值顺序
self.w = b.copy()#修改排列后的物品重量顺序
print("排序后的物品价值,重量和索引为:",self.v, self.w,index)
return self.v, self.w, index
#定义上界函数,注意这里使用了树结构,所以上界函数与回溯法的上界函数有点不同
def bound(self, i, node1):
leftw = self.c - node1.currw#剩余背包容量
bestbound = node1.currv#价值上界
while(i <self.v.size ):
if(self.w[i] <leftw):#全部填充
bestbound = bestbound + self.v[i]
leftw = leftw - self.w[i]
i += 1
else:#部分填充
bestbound = bestbound + self.v[i] / self.w[i] * leftw
break#填充完毕
return bestbound#返回上界值
def branch_bound_method(self, ind):#ind为真实索引
list = []#优先队列
bestindex = set()
list.append(node(0, 0, 0, None, -1))#初始化
while(list != []):#队列不为空则继续遍历
node1 = list.pop(0)#队列第一个结点出来
if(node1.index <self.v.size):#非叶子结点
leftv = node1.currv + self.v[node1.index]
leftw = node1.currw + self.w[node1.index]
left = node(leftv, leftw, node1.index+1, node1, 1)#左结点
left.flag = 1
left.up = self.bound(left.index, left)#计算当前左节点的价值上界
left.x_set=node1.x_set.copy()
left.x_set.append(1)
if(left.currw <=self.c):#如果左节点符合约束条件
list.append(left)#则加入到优先队列
if(left.currv > self.bestx):
self.bestx = left.currv#如果价值超过则更新最大价值
best_x_set=left.x_set
right = node(node1.currv, node1.currw, node1.index+1, node1, 0)#v,w不变,父节点是node1,flag=0,index+1
right.flag = 0
right.up = self.bound(right.index, right)#计算右孩子结点价值上界
right.x_set=node1.x_set.copy()
right.x_set.append(0)
if(right.up >= self.bestx):#若右子树可能包含最优解,则右孩子结点插入到优先队列
list.append(right)
if(node1.index ==self.v.size and list==[]):#如果是叶子结点且优先队列已空则输出最优解
j=0
for i in best_x_set:
j=j+1
if(i==1):
bestindex.add(ind[j-1])#储存最佳选项,根据层级索引映射到真实索引
return self.bestx, bestindex
class node: #定义树结点
def __init__(self, v, w, index, father, flag):
self.currv = v#该结点价值
self.currw = w#该结点重量
self.index = index#对应物品虚拟层级索引
self.up = 0#结点价值上界
self.father = father#该结点的父节点
self.flag = flag#代表取物品或不取该物品
self.x_set=[]
if __name__ == '__main__':
cp=0#当前价值
cw=0#当前重量
bestx=0#当前最高价值
goods=[]
n=int(input("请输入物品数量:"))
c=int(input("请输入背包容量:"))
w=[]
v=[]
for i in range(n):
print(str.format("请输入第{}个物品的重量和价值,空格隔开:",i))
goods.append(list(map(int,input().split())))#使用map将输入映射为列表
w.append(goods[i][0])
v.append(goods[i][1])
question = branchbound(w, v, c, cw , cp ,bestx)
v, w, ind = question.value_sort()
bestp, bestindex = question.branch_bound_method(ind)
print("最大价值为:")
print(bestp)
print("应装的物品为:")
print(bestindex)
对分支限界解决0-1背包问题的理解:
分支限界法有点像回溯法保留了遍历部分,去掉了回溯部分,然后再新增了根据单位价值排序计算整体预估价值的限界函数来剪枝。这样能减少遍历结点,提高效率。