貪欲アルゴリズム、総当たり法、動的プログラミング法を実装して、Python に基づいて分数ナップザック問題と 0-1 ナップザック問題を解決します (完全なソース コードのダウンロード付き)

ナップザック問題のアルゴリズム設計

この問題は、バックパックに入れるアイテムの集合から適切なアイテムを選択する問題ですが、バックパックに入れるアイテムの総重量がバックパックの容量を超えないことを前提として、アイテムの合計値がバックパックに入れるサイズが一番大きくなります。バックパックにアイテムを入れてもよいかどうかの要件により、バックパック問題は「分数バックパック問題」と「0-1バックパック問題」に分けられます

1. 概要設計

  • 分数ナップザック問題。貪欲なアルゴリズムを使用して最適な解を取得します。
  • 0-1 ナップザック問題では、近似解が欲しい場合は貪欲アルゴリズムを使用し、最適解が必要な場合は総当たり法、動的計画法、およびメモリ機能を強化した動的計画法を使用して解決します。 2 つの動的プログラミング メソッドの場合、最終的な結果の動的プログラミング テーブルを返します。

アルゴリズムの具体的な機能設計プロセスは次のとおりです。

2. 具体的なアルゴリズム設計

  • 貪欲なアルゴリズム

① 分数ナップザック問題の最適解を見つけるには、各アイテムの単価を求め、高いものから低いものへ順番にナップザックに入れるアイテムを選択します。ナップザックの容量と等しい場合はナップザックに入れ、それ以外の場合はアイテムをナップザックに入れ、それを分解していくつかのアイテムをバックパックに入れます。バックパックの残容量が0になるとループが停止し、最適な合計値が返されます。機能設計は次のとおりです。

def Greedy_F(n,c):   #贪心算法求解分数背包问题最优解
    #n表示物品个数,c表示背包容量
    global opt1
    Sumvalue1 = 0  #记录背包内物品总价值
    opt1 = [0]*n  #记录选择的物品
    danwei_v = []
    for i in range(n):
        d = v[i]/w[i]    #计算物品单位价值
        danwei_v.append(d)   
    value = list(enumerate(danwei_v))  #enumerate()函数将物品序号与其对应单位价值组合为一组数对
    value.sort(key=lambda a: a[1], reverse=True)  #按物品单位价值降序排序
    while c > 0:
        for i in range(n):
            if  w[value[i][0]] <= c:
                Sumvalue1 += v[value[i][0]]
                opt1[value[i][0]] = w[value[i][0]]
                c -= w[value[i][0]]
            else:
                Sumvalue1 += c*danwei_v[value[i][0]]
                opt1[value[i][0]] = c
        else:
            break
    return Sumvalue1  #返回最优总价值

② 0-1 ナップザック問題の近似解を求めるには、まず各アイテムの単位値を求め、ループ文を使用し、毎回最も単位値の高いアイテムを選択してバックパックに入れます。アイテムがバックパックの容量以下である場合はバックパックに入れます。それ以外の場合は、バックパックの残りの容量が 0 になるかすべてのアイテムが走査されるまで次のアイテムを比較し、ループを停止して最適な合計値を返します。 。機能設計は次のとおりです。

def Greedy_I(n,c):     #贪心算法求解0-1背包近似解
    global opt2
    Sumvalue2 = 0
    opt2 = [0]*n
    danwei_v = []
    for i in range(n):
        d = v[i]/w[i]
        danwei_v.append(d)
    value = list(enumerate(danwei_v))
    value.sort(key=lambda a: a[11], reverse=True)
    while c > 0:
        for i in range(n):
            if  w[value[i][0]] <= c:
                Sumvalue2 += v[value[i][0]]
                opt2[value[i][0]] = 1
                c -= w[value[i][0]]
        else:
            break
    return Sumvalue2
  • ブルートフォース方式

0-1 ナップザック問題の最適解を見つけます。まず、アイテムのすべてのサブセットを徹底的に列挙し、最大値を記録する変数 maxvalue を設定し、すべてのサブセットを走査し、各サブセット内のアイテムの合計重量を計算します。バックパックに積み込むことができ、現在のバックパックの値がmaxvalue より大きい、現在の値 maxvalue に値を代入し、最後にすべての項目の組み合わせをループして最適な解を取得します。関数の設計は次のとおりです。

def Brute(n,c):   #蛮力法求解0-1背包最优解
    a = [0,1]
    l = list(product(a,repeat=n))
    #求解[0,1]中元素的全排列组合,repeat=n表示单个元素最大重复次数
    maxvalue = 0    #记录最大价值
    global opt3
    opt3 = []
    for i in range(len(l)):   #遍历所有子集
        sumweight = 0  # 将总重量与总价值清零,计算下一子集
        sumvalue = 0
        for j in range(n):
            sumweight += l[j][i]*w[j]   #计算子集的总重量
            sumvalue += l[j][i]*v[j]
        if sumweight <= c and sumvalue > maxvalue:   #判断该子集物品能否装入背包,并与最大价值比较进行更新
            maxvalue = sumvalue
            opt3 = list(l[i])
    return maxvalue
  • 動的プログラミング

動的計画法アルゴリズムは、0-1 ナップザック問題の最適解を見つけ、動的計画法テーブルを初期化し、テーブル内のすべての要素が 0 になります。セル F(i,j) は、耐荷重 j のバックパックの最適解における i 個の項目と項目の合計値を表します。再帰関係によれば、次のようになります。

ループを使用してテーブルを 1 行ずつ埋めていきます。最後のセルの値 F(n,c) が必要な最大合計値です。この関数は次のように設計されています:

def DP(n,c):   #动态规划法求解0-1背包问题最优解
    for i in range(1,n+1):
        for j in range(1,c+1):
            if j-w[i-1] < 0:
                F1[i][j] = F1[i-2][j]   #F1为初始化动态规划表,且为全局变量
            else:
                F1[i][j] = max(F1[i-1][j],F1[i-1][j-w[i-1]]+v[i-1])
    return F1[n][c]   #最大总价值
  • メモリ機能により動的プログラミングアルゴリズムが向上

このアルゴリズムの焦点は、ボトムアップ動的プログラミング アルゴリズムで使用されるテーブルと同様のテーブルを維持することです。動的プログラミング テーブルを初期化します。テーブルの最初の行と最初の列の要素は両方とも 0 で、他の要素は -1 です。セルがまだ計算されていないことを示します。F(i,j) は、積載量 j のバックパックの最適解における i 個の項目と項目の合計値を表します。まず、テーブル内のセルの値が 0 より小さいかどうかを確認します。0 より小さい場合は、再帰呼び出しを使用して動的計画法の再帰関係に従って計算し、返された結果をテーブルに記録します。それ以外の場合は、セル内の値を直接返します。機能設計は次のとおりです。

def MFK(i,j):   #记忆功能改进动态规划法
    value = 0
    if F2[i][j] < 0:    #F2为初始化动态规划表,且为全局变量
        if j < w[i-1]:
            value = MFK(i-1,j)
        else:
            value = max(MFK(i-1,j),v[i-1]+MFK(i-1,w[i-1]))
        F2[i][j] = value  #注意缩进
    return F2[i][j]
  • 表のセルをバックトラックして、最適なサブセットのコンポーネントを見つけます。

whileループや条件判定文を使用し、最後のセルからF[i][j]>F[i-1][j]の場合、項目iとF[i-1][jw[i]]を示します。の最適なサブセットが最適解に含まれます。それ以外の場合、アイテム i は最適なサブセットの一部ではありません。バックパックにバックトラックするときに、F[i-1][j] と F[i-2][j] を比較します。残りの容量が 0 の場合、最適解が返されます。機能設計は次のとおりです。

def show(F,n,c):   #F为动态规划表
    global opt4
    opt4 = [0]*n   #记录物品选择状态
    i = n
    j = c
    while c > 0:
        if F[i][j] > F[i-1][j]:
            opt4[i-1] = 1
            j -= w[i - 1]
            c -= w[i - 1]
            i -= 1
        else:
            i -= 1
    return opt4

3. プロジェクトのテスト

次のデータで示される例を考えてみましょう。

  • 分数ナップザック問題

貪欲アルゴリズムで得られた最適な合計値は 38.333 です。最適解は {アイテム 2, アイテム 3, アイテム 4} ​​であり、アイテム 3 の 2/3 だけがバックパックに入れられます。

  • 0-1 バックパック問題

1. 貪欲アルゴリズムは近似解を求めます。合計の最大値は 37 で、近似解は {項目 1、項目 2、項目 4} です。

2. ブルートフォース法、動的計画法、メモリ機能を向上させた動的計画法を用いて最適解を求め、最適合計値は37、最適解は{項目1、項目2、項目4}となります。動的計画法テーブルF1の各セルの値が計算されていることがわかりますが、F2の-1は計算値がない、つまり11個の値しか計算されていないことを示しています。メモリ機能の向上、動的プログラミング方式により効率が向上しました。

テスト結果によると、この例では、グリーディ アルゴリズムで得られた近似解は総当たり法で得られた最適解と同じ、つまり近似解が最適解であることがわかりますが、アルゴリズムは常に最適な解を見つけるとは限りません。反例は次のとおりです。

貪欲アルゴリズムを使用して得られた近似解は {item 1} で、合計値は 40 です。ブルート フォース法を使用して得られた最適解は {item 2, item 3} で、最適な合計値は 50 です。つまり、近似解は最適解ではありません。

完全なソースコードのダウンロード

分数ナップザック問題と 0-1 ナップザック問題を解決するために、Python に基づいて貪欲アルゴリズム、ブルート フォース手法、動的プログラミング手法を実装するソース コード + プロジェクトの説明とコメント.zip

おすすめ

転載: blog.csdn.net/DeepLearning_/article/details/132719797