1. 程式人生 > >動態規劃-揹包問題、兌換零錢問題、旅行商問題

動態規劃-揹包問題、兌換零錢問題、旅行商問題

揹包問題

問題介紹

假定需要將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=0j<volumeii>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)

執行結果

0/1揹包問題執行結果
[0/1揹包問題執行思路圖

兌換零錢問題

問題介紹

假設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]]j<coins[i]j≥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)

執行結果
兌換零錢方法數執行結果
兌換零錢方法數執行思路

求解實際解

採用遞迴的方法可以求出兌換的所有解以及最優解。

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)))

執行結果
求解兌換零錢問題執行結果
使用這個方法求解每類硬幣數量有限時,我們只需對遞推式做出修改便能得到結果,類似於0/1揹包問題。

num(i,j)={num[i1][j],num[i1][j]+num[i1][jcoins[i]]j<coins[i]j≥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])

執行結果

有條件的兌換零錢問題執行結果

旅行商問題【待完成】

滿足三角不等式的旅行商問題

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

一般旅行商問題

其他旅行商問題