簡體   English   中英

用 Python 簡化的 0/1 背包問題

[英]0/1 Knapsack Problem Simplified in Python

我有以下代碼執行速度太慢。 這個想法類似於 0/1 背包問題,你有一個給定的整數 n,你必須找到 1 到 n - 1 范圍內的數字,當平方加起來等於 n 平方。

例如,如果 n 是 5,那么它應該輸出 3 , 4 因為 3 ** 2 和 4 ** 2 = (25 or 5 ** 2)。 我一直在努力理解如何提高效率,並想知道用於提高此類程序效率的概念。

其他一些示例: n = 8 [無] n = 30 [1, 3, 7, 29] n = 16 [2, 3, 5, 7, 13]

我找到了一些關於此的帖子,但它們似乎僅限於兩個數字,因為我的程序需要使用與原始數字相加所需的數量。

我看了一些關於 0/1 背包問題的視頻。 我努力將相同的概念應用到我自己的程序中,因為問題完全不同。 他們有可以放在包里的東西,這些東西有重量和利潤。

這一切都傷害了我的大腦幾個小時,如果有人能指出我正確的方向,我將不勝感激,謝謝:)

from math import sqrt
def decompose(n):

    lst = []

    sets = []

    temp = []

    perm = {}

    out = []

    for i in range (n):
        lst.append(i**2)


    for i in lst:
        for x in sets:
            temp.append(i + x)
            perm[i + x] = (i, x)
        for x in temp:
            if x not in sets:
                sets.append(x)
        if i not in sets:
            sets.append(i)
        temp = []

    if n**2 not in perm.keys():
        return None

    for i in perm[n**2]:
        if str(i).isdigit():
            out.append(i)
        if i == ' ':
            out.append(i)


    for i in out:
        if i not in lst:
            out.remove(i)
            for i in perm[i]:
                if str(i).isdigit():
                    out.append(i)
                if i == ' ':
                    out.append(i)

    out.sort()

    return [sqrt(i) for i in out]

這對於評論來說太大了,所以我把它放在這里作為答案:

這正是 0/1 背包或“硬幣找零問題”(en.wikipedia.org/wiki/Change-making_problem)。 你的目標是賺 25 美分(如果 n = 5)。 你的“硬幣”是 1 美分、4 美分、9 美分、16 美分等。我假設,既然你在看 0/1 背包,你不能重復使用相同的硬幣(如果你可以重復使用相同的硬幣,問題就簡單多了)。

像這樣的動態規划問題有兩種方法。 它們都以自己的方式直觀,但目前對您來說可能更直觀。

1.

第一個是記憶化(稱為自上而下)。 這是您為decompose編寫遞歸函數的地方,但是您緩存了對decompose的每次調用的結果。 這里的遞歸公式類似於

decompose_cache = dictionary that stores results of calls to decompose
def decompose(n = 25, coins_to_use={1,4,9,16}):
  if (n, coins_to_use) in decompose_cache:
    return decompose_cache[(n, coins_to_use)]
  biggest_coin = max(coins_to_use)
  other_coins = coins_to_use - {biggest_coin}
  decomposition_with_biggest_coin = decompose(n-biggest_coin, other_coins)
  decomposition_without_biggest_coin = decompose(n, other_coins)
  ans = decomposition_with_biggest_coin or decomposition_without_biggest_coin
  decompose_cache[(n, coins_to_use)] = ans
  return ans
print(decompose(25, {1,4,9,16}))

也就是說,要確定我們是否可以使用 {1,4,9,16} 賺 25 美分,我們只需要檢查我們是否可以使用 {1,4,9} 賺 25 美分,或者我們是否可以賺 9 美分(25 - 16) 使用 {1,4,9}。 這個遞歸定義,如果我們不緩存每次調用的結果,會導致類似O(n^n)函數調用的結果,但由於我們緩存結果,我們只對某些(目標,硬幣)對進行計算最多一次。 有 n^2 個可能的目標,以及 n 組可能的硬幣,因此有 n^2 * n 對,因此有O(n^2 * n = n^3)函數調用。

2.

第二種方法是動態規划(稱為自底向上)。 (我個人認為這更容易思考,並且您不會在python中遇到最大遞歸深度問題)

這是您填充表格的地方,從空的基本情況開始,表格中條目的值可以通過查看已填充條目的值來計算。 我們可以稱表為“DP”。
在這里,我們可以構建一個表,其中 DP[n][k] 為真,如果您可以僅使用前 k 個“硬幣”(其中第一個硬幣為 1,第二個硬幣為 4 等)求和為 n 的值)。

我們可以計算表格中某個單元格的值的方法是:
DP[n][k] = DP[n - kth coin][k-1] OR DP[n][k-1]

邏輯與上面相同:我們可以用硬幣 {1,4}(前兩個硬幣)找零 5 美分當且僅當我們可以使用 {1} 找零 1 美分(5-4) (第一枚硬幣)或者我們是否可以使用 {1} 找零 5 美分。 因此,DP[5][2] = DP[1][1] 或 DP[5][1]。 同樣,該表有 n^3 個條目。 您可以從 [0][0] 到 [0][5] 逐行填寫,然后從 [0][...] 到 [25][...] 的每一行填寫,答案將在 [25] [5] 中。

這是一個遞歸程序來找到分解。 速度可能不是最佳的。 當然,它不是搜索大范圍輸入的最佳方法,因為當前的方法不緩存中間結果。

在這個版本的函數find_decomposition(n, k, uptonow, used)嘗試僅使用從kn-1的數字來找到n 2的分解,而我們已經使用了used數字集,這些數字給出uptonow的部分總和。 該函數遞歸地嘗試兩種可能性:解決方案包括k本身,或者不包括k 首先嘗試一種可能性,如果有效,則返回它。 如果沒有,請嘗試其他方式。 因此,首先嘗試不使用k的解決方案。 如果它不起作用,請進行快速測試以查看僅使用k是否可以提供解決方案。 如果這還沒有發揮出來,遞歸嘗試一個解決方案,使用k ,因此,對於該組used數字現在還包括k對於其總和uptonow需求將增加k 2。

可以想到許多變體:

  • k可以以相反的順序運行,而不是從1運行到n-1 小心 if 測試的測試條件。
  • 而不是首先嘗試的解決方案不包括k ,開始嘗試一種解決方案,包括k

請注意,對於較大的n ,該函數可能會遇到最大遞歸深度。 例如,當n=1000 ,大約有2 999 個可能的數字子集需要遞歸檢查。 這可能導致 999 級深度的遞歸,這在某些時候對於 Python 解釋器來說太多了。

可能首先使用大量數字的方法可能是有益的,因為它可以迅速減少要填補的空白。 幸運的是,對於大量存在許多可能的解決方案,因此可以快速找到解決方案。 請注意,在@Kevin Wang 描述的一般背包問題中,如果不存在解決方案,則任何具有 999 個數字的方法都需要很長時間才能完成。

def find_decomposition(n, k=1, uptonow=0, used=[]):

    # first try without k
    if k < n-1:
        decomp = find_decomposition(n, k+1, uptonow, used)
        if decomp is not None:
            return decomp

    # now try including k
    used_with_k = used + [k]
    if uptonow + k * k == n * n:
        return used_with_k
    elif k < n-1 and uptonow + k * k + (k+1)*(k+1) <= n * n:
        # no need to try k if k doesn't fit together with at least one higher number
        return find_decomposition(n, k+1, uptonow+k*k, used_with_k)
    return None

for n in range(5,1001):
    print(n, find_decomposition(n))

輸出:

5 [3, 4]
6 None
7 [2, 3, 6]
8 None
9 [2, 4, 5, 6]
10 [6, 8]
11 [2, 6, 9]
12 [1, 2, 3, 7, 9]
13 [5, 12]
14 [4, 6, 12]
15 [9, 12]
16 [3, 4, 5, 6, 7, 11]
...

PS:此鏈接包含有關相關問題的代碼,但方塊可以重復: https : //www.geeksforgeeks.org/minimum-number-of-squares-whose-sum-equals-to-given-number-n/

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM