动态规划-背包问题、兑换零钱问题、旅行商问题

背包问题

问题介绍

假定需要将Goods={g1,g1,…gn}放入容量为ALL的背包内,volumej代表第i个物品的体积,valuej代表第j个物品的价值。我们要把这些物品装进背包,这些物品的体积不超过ALL,而且要使它们的总价值达到最大。因为背包不可包含一个以上的同类物品,所以一般称这样的背包问题为0/1背包问题,完全背包问题类似于兑换零钱问题。

解决思路

设C[i,j]表示从前i项{g1,g1,…gn}取出来装入体积为j的背包的物品的最大价值,其中i∈[0,n],j∈[0,ALL],最终chart[n,ALL]是我们最终想要得到的结果。可以通过观察得到以下结论:
C[i,j]为下面两个量的最大值。
①V[i-1,j]:使用最优的方法将{g1,g1,…gi-1}放入容量为j的背包所得的最大价值。
②V[i-1,j-volumei]+value[i]:使用最优的方法将{g1,g1,…gi-1}放入容量为j-volumei的背包所得的最大价值再加上物品gi的价值。

递推式

C(i,j)=0,C[i1,j],max(C[i1,j],C[i1,jvolumei]+valuei)i=0 j=0jii>0j≥volumei


Python

使用标志矩阵输出装在背包里的物品

# coding = utf-8


class Goods:

    def __init__(self, name=None, volume=0, value=0):
        self.name = name
        self.volume = volume
        self.value = value

    def get_name(self):
        return self.name

    def get_volume(self):
        return self.volume

    def get_value(self):
        return self.value


def knapasck(lst, all):
    """
    利用动态规划求解背包问题
    :param lst:
    :param all:
    :return:
    """
    chart = [[0 for col in range(all + 1)]
             for row in range(lst.__len__() + 1)]  # 初始化结果矩阵,并将其值全部置为0

    chart_flag = [[[] for col in range(all + 1)]
                  for row in range(lst.__len__() + 1)]  # 初始化标志矩阵,并将其值全部置为[]

    for row in range(lst.__len__() + 1):
        for col in range(all + 1):
            if 0 == row or 0 == col:
                chart[row][col] = 0
            elif col < lst[row - 1].get_volume():
                chart[row][col] = chart[row - 1][col]
                chart_flag[row][col] = chart_flag[row - 1][col]
            elif row > 0 and col >= lst[row - 1].get_volume():
                chart[row][col] = max(chart[row - 1][col], chart[row - 1][col - lst[row - 1].get_volume()] + lst[row - 1].get_value())
                if chart[row][col] == chart[row - 1][col]:
                    chart_flag[row][col] = chart_flag[row - 1][col]
                else:
                    chart_flag[row][col] = chart_flag[row - 1][col - lst[row - 1].get_volume()] + [lst[row - 1].get_name()]
    return chart, chart_flag


if __name__ == "__main__":
    goods_lst = [
        Goods('a', 2, 3),
        Goods('b', 3, 4),
        Goods('c', 4, 5),
        Goods('d', 5, 7)]
    knapasck_chart, knapasck_flag = knapasck(goods_lst, all=9)
    # 显示计算列表和标记列表
    print('结果矩阵如下:')
    for i in knapasck_chart:
        print(i)
    print("标志矩阵如下:")
    for j in knapasck_flag:
        print(j)

运行结果

动态规划-背包问题、兑换零钱问题、旅行商问题_第1张图片
动态规划-背包问题、兑换零钱问题、旅行商问题_第2张图片


兑换零钱问题

问题介绍

假设2角、3角、5角和6角硬币无限个,给一个固定的金额,求可置换硬币的若干个方法,类似于完全背包问题,即物品在条件允许下可以选择任意个。

解决思路

num[i][j]表示前i个硬币兑换的金额为j时一共有多少种兑换方法,coins代表硬币的种类(降序排序好的列表)。
为了计算方便,我们假设兑换金额为0时有一种方法,那就是什么都不选。
我们最终得到以下结论
①如果需要兑换的金额小于当前i表示硬币的面值,则num[i][j]的值为num[i-1][j];
②如果需要兑换的金额大于当前j表示硬币的面值,则num[i][j]的值为num[i-1][j]+num[i-1][j-a[i]]。

递推式

num(i,j)={num[i1][j],num[i1][j]+num[i][jcoins[i]]jj≥coins[i]


Python

获得零钱兑换的方法数

def money_change(lst, money=0):
    """
    兑换零钱
    :param lst:零钱面值
    :param money:金额
    :return:
    """
    num = [[0 for col in range(money + 1)]
             for row in range(lst.__len__())]  # 初始化结果矩阵,并将其值全部置为1
    for row in range(0, lst.__len__()):
        num[row][0] = 1  # 设定第一列为1,表示金额为0时,仍有一种兑换方法

    for row in range(1, lst.__len__()):
        for col in range(money + 1):
            if col < lst[row]:
                num[row][col] = num[row - 1][col]
            else:
                num[row][col] = num[row - 1][col] + num[row][col - lst[row]]
    return num


if __name__ == "__main__":
    coins = [0, 2, 3, 5, 6]
    num_lst = []
    min_num = 0
    for element in money_change(coins, 10):
        print(element)

运行结果
动态规划-背包问题、兑换零钱问题、旅行商问题_第3张图片
动态规划-背包问题、兑换零钱问题、旅行商问题_第4张图片


求解实际解

采用递归的方法可以求出兑换的所有解以及最优解。


Python

# coding = utf-8


ALL_METHOD = []  # 定义全局变量保存所有解决办法


class Method:

    info = ''
    length = 0

    def __init__(self, info, length=0):
        self.info = info
        self.length = length


def money_change(lst, method_lst, money=0):
    """
    兑换零钱
    :param lst:零钱面值
    :param method_lst: 解决办法列表
    :param money:金额
    :return:
    """
    temp = []  # 用于储存当前计算的结果
    global ALL_METHOD  # 声明为全局变量
    if money > 0 and len(lst) > 0:
        temp.extend(method_lst)
        if len(lst) > 1:
            money_change(lst[0: len(lst) - 1], temp, money)
        if money >= lst[len(lst) - 1]:
            method_lst.append(lst[len(lst) - 1])
            temp.clear()
            temp.extend(method_lst)
            money_change(lst[0: len(lst)], temp, money - lst[len(lst) - 1])
    elif money <= 0:
        ALL_METHOD.append(Method(method_lst, len(method_lst)))


if __name__ == "__main__":
    coins = [2, 3, 5, 6]
    num_lst = []
    min_num = 0
    money_change(coins, [], 10)
    for element in ALL_METHOD:
        print(element.info)
    ALL_METHOD.sort(key=lambda method: method.length)  # 排序
    for element in ALL_METHOD:
        if element.length == ALL_METHOD[0].length:
            print("兑换的最小个数为{},组成为:{}".format(element.length, sorted(element.info)))

运行结果
动态规划-背包问题、兑换零钱问题、旅行商问题_第5张图片
使用这个方法求解每类硬币数量有限时,我们只需对递推式做出修改便能得到结果,类似于0/1背包问题。

num(i,j)={num[i1][j],num[i1][j]+num[i1][jcoins[i]]jj≥coins[i]

修改下列代码

money_change(lst[0: len(lst)], temp, money - lst[len(lst) - 1])
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
money_change(lst[0: len(lst) - 1 ], temp, money - lst[len(lst) - 1])


运行结果

动态规划-背包问题、兑换零钱问题、旅行商问题_第6张图片


旅行商问题【待完成】

满足三角不等式的旅行商问题

即满足c(u,w)≤c(u,v)+c(v,w)


一般旅行商问题


其他旅行商问题

你可能感兴趣的:(算法入门)