前面提到:
不知道大家注意到没有?上述实现方式没有使用单位体积价值的排序,和之前提到01背包回溯法基于单位体积价值实现不一样(先装单位体积价值高的)。
我们网上经常看到都是基于以上实现的,到底这个用有什么好处了?实际上基于排序的单位体积价值是一个非常精确的限界函数
基于优先级队列的实现方式,就需要用到以上的结构:
优先级队列方式需要数据预处理,为什么需要预处理数据了,这里使用的是优先级队列,这个优先级的选区非常重要,也非常重要
之前的策略curValue+rest就不行了,不是不行,是不够好,那是一个粗糙的界,可以看到一开始选的就不是最优,然后又往回跳跃,因为在这种策略下,无法保证最优,这时需要精心挑选优先级,使之尽可能少往回跳跃或者跳低点,就像思考贪心策略那样,实际上就是思考贪心策略,贪心就是配合优先级队列使用的这里首先把数据安装单位体积价值降序排列。
代码实现如下:
import heapq
class Nodes:
def __init__(self,CurValue=None,CurCost=None,depth =None,parent=None,Flag=None):
# 部分解所占体积
self.CurCost = CurCost
# 部分解所占价值
self.CurValue = CurValue
# 处于那一层
self.depth = depth
# 当前结点是否选择了物品
self.isleft = Flag
# 前一个结点是谁
self.parent = parent
class PQ_pack_01_with_solution_tracking:
def __init__(self,N,V,C,W):
self.num = N
self.volume = V
self.cost = C
self.value = W
#(0当前的价值,1,当前体积,2当前的深度,3父节点,4是否选择了该物品)
self.bestnode = Nodes(0,0,0,None,False)
# 保存最后的方案
self.bestsolution = [False]*N
# 数据预处理,为什么需要预处理数据了,这里使用的是优先级队列,这个优先级的选区非常重要,也非常重要
# 之前的策略curValue+rest就不行了,因为在这种策略下,无法保证最优,这时需要精心挑选优先级,就像
# 思考贪心策略那样,实际上就是思考贪心策略,贪心就是配合优先级队列使用的
# 这里首先把数据安装单位体积价值降序排列,self.order记录与之前排列的标号,用于恢复最后结果
self._cost,self._value,self.order = self.sort_group(N,C,W)
# 把数据安装单位体积价值降序排列
def sort_group(self,N,C,W):
# 原来数据的标号
O = [i for i in range(N)]
perp = [0]*N
for i in range(N):
perp[i] = W[i]/C[i]
for i in range(N-1):
for j in range(i+1,N):
if perp[i] < perp[j]:
temp = perp[i]
perp[i] = perp[j]
perp[j] = temp
temp = O[i]
O[i] = O[j]
O[j] = temp
temp = C[i]
C[i] = C[j]
C[j] = temp
temp = W[i]
W[i] = W[j]
W[j] = temp
return C,W,O
# 限界函数,这个限界函数就非常精确,确保了每一次往下都是最优的策略
def bound(self,depth,CurCost,CurValue):
left_weight = self.volume - CurCost
b = CurValue
while depth < self.num and self._cost[depth] <= left_weight:
left_weight -=self._cost[depth]
b += self._value[depth]
depth +=1
if depth < N:
b += (self._value[depth]/self._cost[depth]) * left_weight
return b
def PQ_pack_01(self):
pqueue = []
# 初始化,从root开始
current_node = None
current_value = 0
current_cost = 0
depth = 0
# 终止条件,优先级队列里面存的是(0当前的价值,1,当前体积,2当前的深度,3父节点,4是否选择了该物品)
# 只要取第self.num层最优的current_value就可以了,只要到self.num层终止就行了
while depth != self.num:
# 满足约束条件,存入左结点
if current_cost + self._cost[depth] <= self.volume:
# 每次进入左结点更新最优质,这样方便剪枝,让没有必要的点不放进优先级队列
if current_value + self._value[depth] > self.bestnode.CurValue:
self.bestnode.CurValue =current_value + self._value[depth]
# 确定待放入结点的上界
temp = self.bound(depth+1,current_cost+self._cost[depth],current_value+self._value[depth])
# 把待放入结点的上界,当前价值,当前花费,当前层次,父亲,是左是右放入优先级队列
# 因为是要求最大堆,随意优先级取了-,heapq默认是取最小堆,直接求最大堆的包还不知道怎么用
heapq.heappush(pqueue,(-temp,Nodes(current_value + self.value[depth],current_cost+self._cost[depth],depth+1,current_node,True)))
# 对于右结点计算上界
up = self.bound(depth+1,current_cost,current_value)
# 加入上界小于当前最优质,就没有必要放入优先级队列,免得给优先级队列增加负担
# 等于的情况需要放进去,因为这时路径必须的,没有等于0,就没法深入了
if up >= self.bestnode.CurValue:
heapq.heappush(pqueue,(-up,Nodes(current_value,current_cost,depth+1,current_node,False)))
# 弹出下一个最优的结点,0代表上界,1包含了所需要的信息
current_node = heapq.heappop(pqueue)[1]
current_value = current_node.CurValue
current_cost = current_node.CurCost
depth = current_node.depth
print(depth,current_value)
self.bestnode = current_node
print(self.bestnode.CurValue)
# 追踪解
def solution_tracking(self):
# 追踪解,获取最优方案
BestResult =[False]*N
for i in range(self.num -1,-1,-1):
BestResult[i] = self.bestnode.isleft
self.bestnode = self.bestnode.parent
# 将最优方案翻译成原来的排序
for i in range(N):
if BestResult[i]:
self.bestsolution[self.order[i]] = True
print(self.bestsolution)
N = 8
V = 30
C = [11,2,3,9,13,6,15,7]
W = [5.0,2.0,5.0,7.0,5.0,11.0,6.0,14.0]
tt =PQ_pack_01_with_solution_tracking(N,V,C,W)
tt.PQ_pack_01()
tt.solution_tracking()
1 14.0
2 25.0
3 30.0
4 32.0
5 39.0
6 39.0
7 39.0
4 30.0
8 39.0
39.0
[False, True, True, True, False, True, False, True]