python创建树结点——分支限界法解决0-1背包问题

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背包问题的理解:
分支限界法有点像回溯法保留了遍历部分,去掉了回溯部分,然后再新增了根据单位价值排序计算整体预估价值的限界函数来剪枝。这样能减少遍历结点,提高效率。

  • 5
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
算法思想: 分支限界法是一种在求解问题的所有可行解中,搜索最优解的算法。该算法采用广度优先搜索策略,对于每个节点,将其扩展为若干个子节点,计算子节点的上界或者下界,根据上界或者下界的大小,将子节点按照某种顺序排列,将最有可能包含最优解的子节点放在最前面,依次搜索扩展子节点,直到找到最优解为止。 对于0-1背包问题分支限界法可以采用贪心法计算上界,即将背包容量按照性价比从大到小排序,然后依次将物品放入背包中,直到放不下为止。对于下界的计算,可以将剩余物品的价值全部加到当前背包的价值中,这样得到的价值就是当前背包能够得到的最大价值下界。 效率分析: 分支限界法的时间复杂度取决于搜索的深度和每个节点的扩展子节点数目。对于0-1背包问题,搜索的深度最多为n,每个节点最多扩展两个子节点,因此时间复杂度为O(2^n)。 Python代码: ```python class Node: def __init__(self, level, weight, value): self.level = level self.weight = weight self.value = value self.bound = 0 def bound(node, n, W, w, v): if node.weight >= W: return 0 else: max_bound = node.value j = node.level + 1 totweight = node.weight while j < n and totweight + w[j] <= W: max_bound += v[j] totweight += w[j] j += 1 if j < n: max_bound += (W - totweight) * v[j] / w[j] return max_bound def knapsack_bfs(n, W, w, v): queue = [] root = Node(-1, 0, 0) queue.append(root) max_value = 0 while len(queue) > 0: node = queue.pop(0) if node.level == n - 1: continue left = Node(node.level + 1, node.weight + w[node.level + 1], node.value + v[node.level + 1]) if left.weight <= W and left.value > max_value: max_value = left.value left.bound = bound(left, n, W, w, v) if left.bound > max_value: queue.append(left) right = Node(node.level + 1, node.weight, node.value) right.bound = bound(right, n, W, w, v) if right.bound > max_value: queue.append(right) return max_value # 测试案例 n = 5 W = 10 w = [2, 2, 6, 5, 4] v = [6, 3, 5, 4, 6] print(knapsack_bfs(n, W, w, v)) # 输出 15 ``` 测试案例: 我们使用如下的测试案例来验证算法的正确性: n = 5 W = 10 w = [2, 2, 6, 5, 4] v = [6, 3, 5, 4, 6] 其中n表示物品的个数,W表示背包的容量,w表示物品的重量,v表示物品的价值。该测试案例中的最优解为15,即将第1、2、5个物品放入背包中。 运行代码后可以得到正确的结果15。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值