62、分组背包问题(MCKP)Greedy、Dyer-Zemel、对偶界、动态规划实现

原文链接:https://link.springer.com/chapter/10.1007/978-3-540-24777-7_11

不同于传统的背包问题,MCKP需要保证每个分组里面至少选一个物品,建模如下

 

其提出线性支配(LP-dominated)的定义如下

这是个筛选的过程,把处于支配(同一组内存在比你重量小但价值高的物品)的去掉,线性支配的定义会更强,比如3和7,其被线性支配,故被去掉,我个人理解这样在部分情况有可能把最优解去掉了,所以一般采用前者。

 

纯贪心算法如下

class Good():
    def __init__(self, row, col, p, w):
        self.row = row
        self.col = col
        self.p = p
        self.w = w

    def __repr__(self):
        return str((self.w, self.p))

def goods_filter_LP_dominated(goods, exacting=True):

    [goods[i].sort(key=lambda n:n.w, reverse=False) for i in range(len(goods))]

    goods1 = [[goods[i][0]] for i in range(len(goods))]
    for i in range(len(goods)):
        p, w = goods1[i][0].p, goods1[i][0].w
        for j in range(1, len(goods[i])):
            if goods[i][j].w == w and goods[i][j].p > p:
                goods1[i][-1] = goods[i][j]
                p = goods[i][j].p
            elif goods[i][j].w > w and goods[i][j].p > p:
                goods1[i].append(goods[i][j])
                p, w = goods[i][j].p, goods[i][j].w

    # 严格LP_dominated
    if exacting:
        goods2 = [[goods1[i][0]] for i in range(len(goods1))]
        for i in range(len(goods1)):
            j = 0
            while j < len(goods1[i]) - 1:
                slopes = [[k, (goods1[i][k].p - goods1[i][j].p) / (goods1[i][k].w - goods1[i][j].w)] for k in range(j + 1, len(goods1[i]))]
                slopes.sort(key=lambda x: x[1], reverse=True)
                j = slopes[0][0]
                goods2[i].append(goods1[i][j])
        goods1 = goods2

    return goods1


#每次更新都选择局部提升最大的
def Greedy(goods, c):

    goods = goods_filter_LP_dominated(goods, exacting=True)

    # 做成链表的形式,利于贪心算法
    class Node():
        def __init__(self, good):
            self.good = good
            self.improve = float("-inf")
            self.next = None
        def __repr__(self):
            return str(self.good)

    L_Node = []
    for i in range(len(goods)):
        n = Node(goods[i][0])
        temp = n
        j = 0
        while j+1 < len(goods[i]):
            j += 1
            temp.next = Node(goods[i][j])
            temp.improve = (goods[i][j].p - goods[i][j-1].p) / (goods[i][j].w - goods[i][j-1].w)
            temp = temp.next
        L_Node.append(n)

    # 初始化可行解Y,使用空间d,价值z
    d = sum([L_Node[i].good.w for i in range(len(L_Node))])

    is_end = False
    while not is_end:
        is_end = True
        L_Node.sort(key=lambda x: x.improve, reverse=True)
        for i in range(len(L_Node)):
            if L_Node[i].next and L_Node[i].next.good.w - L_Node[i].good.w <= c-d:
                d += L_Node[i].next.good.w - L_Node[i].good.w
                L_Node[i] = L_Node[i].next
                is_end = False
                break

    obj = sum([n.good.p for n in L_Node])
    cost = sum([n.good.w for n in L_Node])

    res_choose = [L_Node[i].good for i in range(len(L_Node))]
    res_choose.sort(key=lambda x:x.row)
    return res_choose, obj, cost


def run_one():

    #不一定最优、反例
    # P = [
    #     [25, 55, 65, 76, 40, 20, 80, 50, 89, 30, 82],
    #     [47, 20, 50, 95, 70, 40, 75, 30],
    #     [35, 10, 59, 20, 71, 15, 45, 40, 80]
    # ]
    #
    # W = [
    #     [7, 12, 24, 26, 35, 45, 51, 58, 65, 85, 90],
    #     [12, 20, 24, 36, 50, 60, 80, 84],
    #     [8, 10, 24, 30, 40, 55, 60, 80, 85]
    # ]

    P = [
        [76, 49, 79, 41, 25, 78, 31, 55, 63, 19, 89],
        [95, 17, 36, 47, 29, 51, 84, 73],
        [35, 39, 20, 51, 24, 71, 59, 80, 14]
    ]

    W = [
        [26, 62, 84, 35, 7, 54, 82, 12, 23, 50, 65],
        [36, 25, 62, 12, 91, 28, 73, 54],
        [8, 85, 61, 62, 30, 40, 24, 85, 11]
    ]
    c = 100

    goods = [[Good(i, j, P[i][j], W[i][j]) for j in range(len(W[i]))] for i in range(len(W)) if len(W[i]) > 0]
    res_choose_greedy, obj_greedy, cost_greedy = Greedy(goods, c)
    print("\nres_choose_greedy:", res_choose_greedy)
    print("obj_greedy:", obj_greedy)
    print("cost_greedy:", cost_greedy)


if __name__ == '__main__':
    run_one()

Dyer-Zemel算法如下

import numpy as np
from MCKP.method.MCKP_Greedy import *

#用中位数去筛选边,有点lagrangian的意思
def Dyer_Zemel(goods, c):

    goods = goods_filter_LP_dominated(goods, exacting=False)

    while True:
        slopes = [(goods[i][j+1].p - goods[i][j].p) / (goods[i][j+1].w - goods[i][j].w) for i in range(len(goods)) for j in range(len(goods[i])-1)]
        alpha = np.median(slopes)
        values_alpha = [[goods[i][j].p - alpha*goods[i][j].w for j in range(len(goods[i]))] for i in range(len(goods))]
        L_a = [values_alpha[i].index(max(values_alpha[i])) for i in range(len(values_alpha))]
        L_b = [L_a[i]+1 if L_a[i] < len(values_alpha[i])-1 and values_alpha[i][L_a[i]] == values_alpha[i][L_a[i]+1] else L_a[i] for i in range(len(values_alpha))]
        w_a_sum = sum([goods[i][L_a[i]].w for i in range(len(goods))])
        w_b_sum = sum([goods[i][L_b[i]].w for i in range(len(goods))])
        if w_a_sum > c:
            goods = [[goods[i][j] for j in range(len(goods[i])) if j == 0 or (j > 1 and (goods[i][j].p - goods[i][j-1].p) / (goods[i][j].w - goods[i][j-1].w)) >= alpha] for i in range(len(goods))]
        elif w_b_sum < c:
            goods = [[goods[i][j] for j in range(len(goods[i])) if j == len(goods[i])-1 or (j < len(goods[i])-1 and (goods[i][j+1].p - goods[i][j].p) / (goods[i][j+1].w - goods[i][j].w)) <= alpha] for i in range(len(goods))]
        else:
            break

    goods1 = [[goods[i][L_a[i]], goods[i][L_b[i]]] if L_a[i] < L_b[i] else [goods[i][L_a[i]]] for i in range(len(goods))]

    return Greedy(goods1, c)


def run_one():

    P = [
        [76, 49, 79, 41, 25, 78, 31, 55, 63, 19, 89],
        [95, 17, 36, 47, 29, 51, 84, 73],
        [35, 39, 20, 51, 24, 71, 59, 80, 14]
    ]

    W = [
        [26, 62, 84, 35, 7, 54, 82, 12, 23, 50, 65],
        [36, 25, 62, 12, 91, 28, 73, 54],
        [8, 85, 61, 62, 30, 40, 24, 85, 11]
    ]
    c = 100

    goods = [[Good(i, j, P[i][j], W[i][j]) for j in range(len(W[i]))] for i in range(len(W)) if len(W[i]) > 0]
    res_choose_Dyer_Zemel, obj_Dyer_Zemel, cost_Dyer_Zemel = Dyer_Zemel(goods, c)
    print("\nres_choose_Dyer_Zemel:", res_choose_Dyer_Zemel)
    print("obj_Dyer_Zemel:", obj_Dyer_Zemel)
    print("cost_Dyer_Zemel:", cost_Dyer_Zemel)


if __name__ == '__main__':
    run_one()

 

其对偶问题如下

class Good():
    def __init__(self, row, col, p, w):
        self.row = row
        self.col = col
        self.p = p
        self.w = w

    def __repr__(self):
        return str((self.w, self.p))

def goods_filter_LP_dominated(goods, exacting=True):

    [goods[i].sort(key=lambda n:n.w, reverse=False) for i in range(len(goods))]

    goods1 = [[goods[i][0]] for i in range(len(goods))]
    for i in range(len(goods)):
        p, w = goods1[i][0].p, goods1[i][0].w
        for j in range(1, len(goods[i])):
            if goods[i][j].w == w and goods[i][j].p > p:
                goods1[i][-1] = goods[i][j]
                p = goods[i][j].p
            elif goods[i][j].w > w and goods[i][j].p > p:
                goods1[i].append(goods[i][j])
                p, w = goods[i][j].p, goods[i][j].w

    # 严格LP_dominated
    if exacting:
        goods2 = [[goods1[i][0]] for i in range(len(goods1))]
        for i in range(len(goods1)):
            j = 0
            while j < len(goods1[i]) - 1:
                slopes = [[k, (goods1[i][k].p - goods1[i][j].p) / (goods1[i][k].w - goods1[i][j].w)] for k in range(j + 1, len(goods1[i]))]
                slopes.sort(key=lambda x: x[1], reverse=True)
                j = slopes[0][0]
                goods2[i].append(goods1[i][j])
        goods1 = goods2

    return goods1

def L(goods, u, c):
    return u*c + sum([max([g.p-u*g.w for g in goods[i]]) for i in range(len(goods))])

def U2_generate(goods, r, c, step_min):
    left = 0
    right = 1
    # 先采用放缩法确定寻优区间
    while L(goods, right * (2 - r), c) < L(goods, right, c):
        right = right * (2 - r)
    right = right * (2 - r)

    # 采用三分法确定步长
    mid1 = left * 2 / 3 + right / 3
    mid2 = left / 3 + right * 2 / 3
    while abs(right - left) > step_min:
        if L(goods, mid1, c) < L(goods, mid2, c):
            right = mid2
        else:
            left = mid1
        mid1 = left * 2 / 3 + right / 3
        mid2 = left / 3 + right * 2 / 3

    return L(goods, left, c)

#每次更新都选择局部提升最大的
def Lagrangian(goods, c, r=0.8, step_min=0.00001):

    goods = goods_filter_LP_dominated(goods, exacting=False)

    return U2_generate(goods, r, c, step_min)


def run_one():

    #不一定最优、反例
    # P = [
    #     [25, 55, 65, 76, 40, 20, 80, 50, 89, 30, 82],
    #     [47, 20, 50, 95, 70, 40, 75, 30],
    #     [35, 10, 59, 20, 71, 15, 45, 40, 80]
    # ]
    #
    # W = [
    #     [7, 12, 24, 26, 35, 45, 51, 58, 65, 85, 90],
    #     [12, 20, 24, 36, 50, 60, 80, 84],
    #     [8, 10, 24, 30, 40, 55, 60, 80, 85]
    # ]

    P = [
        [76, 49, 79, 41, 25, 78, 31, 55, 63, 19, 89],
        [95, 17, 36, 47, 29, 51, 84, 73],
        [35, 39, 20, 51, 24, 71, 59, 80, 14]
    ]

    W = [
        [26, 62, 84, 35, 7, 54, 82, 12, 23, 50, 65],
        [36, 25, 62, 12, 91, 28, 73, 54],
        [8, 85, 61, 62, 30, 40, 24, 85, 11]
    ]
    c = 100

    goods = [[Good(i, j, P[i][j], W[i][j]) for j in range(len(W[i]))] for i in range(len(W)) if len(W[i]) > 0]
    U2 = Lagrangian(goods, c)
    print("\nU2:", U2)


if __name__ == '__main__':
    run_one()

 

动态规划

class Good():
    def __init__(self, row, col, p, w):
        self.row = row
        self.col = col
        self.p = p
        self.w = w

    def __repr__(self):
        return str((self.w, self.p))

def goods_filter_LP_dominated(goods, exacting=True):

    [goods[i].sort(key=lambda n:n.w, reverse=False) for i in range(len(goods))]

    goods1 = [[goods[i][0]] for i in range(len(goods))]
    for i in range(len(goods)):
        p, w = goods1[i][0].p, goods1[i][0].w
        for j in range(1, len(goods[i])):
            if goods[i][j].w == w and goods[i][j].p > p:
                goods1[i][-1] = goods[i][j]
                p = goods[i][j].p
            elif goods[i][j].w > w and goods[i][j].p > p:
                goods1[i].append(goods[i][j])
                p, w = goods[i][j].p, goods[i][j].w

    # 严格LP_dominated
    if exacting:
        goods2 = [[goods1[i][0]] for i in range(len(goods1))]
        for i in range(len(goods1)):
            j = 0
            while j < len(goods1[i]) - 1:
                slopes = [[k, (goods1[i][k].p - goods1[i][j].p) / (goods1[i][k].w - goods1[i][j].w)] for k in range(j + 1, len(goods1[i]))]
                slopes.sort(key=lambda x: x[1], reverse=True)
                j = slopes[0][0]
                goods2[i].append(goods1[i][j])
        goods1 = goods2

    return goods1


#每次更新都选择局部提升最大的
def Dynamic(goods, c):

    goods = goods_filter_LP_dominated(goods, exacting=False)

    class Combination():
        def __init__(self, P, W, L):
            self.P = P
            self.W = W
            self.L = L

        def __repr__(self):
            return str((self.W, self.P))

    C = {goods[0][j].w: Combination(goods[0][j].p, goods[0][j].w, [goods[0][j]]) for j in range(len(goods[0]))}

    for i in range(1, len(goods)):
        C_next = {}
        for j in range(len(goods[i])):
            for k,v in C.items():
                if goods[i][j].w + k > c:
                    continue
                #不在或价值更高则更新
                if (goods[i][j].w + k not in C_next) or (goods[i][j].w + k in C_next and goods[i][j].p + v.P > C_next[goods[i][j].w + k].P):
                    C_next[goods[i][j].w + k] = Combination(goods[i][j].p + v.P, goods[i][j].w + k, v.L+[goods[i][j]])
        C = C_next

    res = list(C.values())
    res.sort(key=lambda x:x.P, reverse=True)
    res_choose = res[0].L
    obj = res[0].P
    cost = res[0].W
    return res_choose, obj, cost


def run_one():

    #不一定最优、反例
    # P = [
    #     [25, 55, 65, 76, 40, 20, 80, 50, 89, 30, 82],
    #     [47, 20, 50, 95, 70, 40, 75, 30],
    #     [35, 10, 59, 20, 71, 15, 45, 40, 80]
    # ]
    #
    # W = [
    #     [7, 12, 24, 26, 35, 45, 51, 58, 65, 85, 90],
    #     [12, 20, 24, 36, 50, 60, 80, 84],
    #     [8, 10, 24, 30, 40, 55, 60, 80, 85]
    # ]

    P = [
        [76, 49, 79, 41, 25, 78, 31, 55, 63, 19, 89],
        [95, 17, 36, 47, 29, 51, 84, 73],
        [35, 39, 20, 51, 24, 71, 59, 80, 14]
    ]

    W = [
        [26, 62, 84, 35, 7, 54, 82, 12, 23, 50, 65],
        [36, 25, 62, 12, 91, 28, 73, 54],
        [8, 85, 61, 62, 30, 40, 24, 85, 11]
    ]
    c = 100

    goods = [[Good(i, j, P[i][j], W[i][j]) for j in range(len(W[i]))] for i in range(len(W)) if len(W[i]) > 0]
    res_choose_Dynamic, obj_Dynamic, cost_Dynamic = Dynamic(goods, c)
    print("\nres_choose_Dynamic:", res_choose_Dynamic)
    print("obj_Dynamic:", obj_Dynamic)
    print("cost_Dynamic:", cost_Dynamic)


if __name__ == '__main__':
    run_one()

 

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 贪心最佳优先搜索(Greedy Best-First Search)是一种启发式搜索算法,它通过在每个搜索步骤中优先考虑最有可能导致解决方案的节点,来寻找最优解。与广度优先搜索不同,贪心最佳优先搜索使用一个评估函数来衡量搜索状态的好坏,然后按照评估函数的值来选择下一个要扩展的节点。贪心最佳优先搜索通常用于解决状态空间较大,但具有良好的启发信息的问题。 ### 回答2: 贪婪最优先搜索(GBFS)是一种启发式搜索算法,其基本思想是根据一定的启发函数,尽可能快地向目标状态方向搜索。它的搜索过程类似于最优先搜索,但是它不使用精确的代价函数,而是使用启发函数来估计从当前状态到目标状态的代价。 GBFS通过选择启发函数中具有最小估计代价的节点来扩展搜索树,因此它可能会快速地向目标状态移动。然而,由于它只考虑了估计代价,而没有考虑实际代价,因此它有可能陷入死胡同或者过早的终止搜索。 与最优先搜索类似,GBFS也具有一个开放列表,其中包含了待搜索的节点。通过比较启发函数的值,GBFS选择启发值最小的节点进行扩展。当搜索到达目标状态时,GBFS停止搜索。 与其他搜索算法相比,GBFS具有以下优缺点: 优点: 1. 它可以快速地向目标状态移动。 2. 它在内存和计算资源上比A*搜索更加高效。 缺点: 1. 它可能陷入局部最优解并忽略其他有可能更优的解。 2. 它无法保证找到最优解。 3. 它存在一个问题,即当启发函数不准确时,可能会浪费大量的时间和计算资源搜索不必要的节点。 总的来说,GBFS对于大多数问题而言是一个高效的搜索算法,但是它的表现取决于使用的启发函数的准确性。 ### 回答3: 贪婪最佳优先搜索(Greedy Best-First Search)是一种启发式搜索算法,它选取可行节点中最有希望的节点作为下一个扩展节点。在贪婪最佳优先搜索中,每个节点都被赋予一个估价函数的值,估价函数用于估计从当前节点到目标节点的最小代价或距离,该算法通常用于解决单目标问题,如路径规划或机器人导航。 贪婪最佳优先搜索过程中,节点状态用一个优先队列来维护。节点的优先级由估价函数的值决定,即节点的估价函数值越小,则节点的优先级越高,队列中位置越靠前。这种贪心策略导致该算法的效率很高,但是不能保证它能找到全局最优解,它只能找到靠近目标的局部最优解。 贪婪最佳优先搜索的优点是速度快,但是它有缺陷。该算法只能找到越来越接近终点的节点,但是它不能保证一些没有被考虑的位于路径上的节点,这些节点可能会导致更短或更优的路径。因此,该算法只适用于简单问题,但是在复杂问题上找到最优解的准确性不可靠。 在实际问题中,我们通常使用A*算法代替贪婪最佳优先搜索。A*算法既具备贪婪最佳优先搜索的速度和效率,又能优化贪婪算法的不足之处,它能确保找到最优解,并取得了广泛的应用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值