簡體   English   中英

如何找到總和最接近 K 但在多列上的 N 個數字?

[英]How to find N numbers whose sum is closest to K , but over multiple columns?

我正在嘗試解決一個優化問題,該問題包括尋找子集總和問題的最佳解決方案,但是,我們需要找到一個解決方案,其中每列的總和最接近每列的唯一數字。 另一個約束是它應該是表中只有 45 行的總和。

我已經嘗試過使用 Bruteforce,但它只會耗盡系統資源。 根據我對問題的理解,這是背包問題的一個子集,稱為子集求和問題,但我想在多列上執行此操作。

為了更好地說明問題

Label | Weight | Parameter 1 | Parameter 2 | Parameter 3
Item1 |   12   |     13      |    91       |      24
Item2 |   76   |     12      |    10       |      14
Item3 |   43   |     11      |    34       |      35
Item4 |   23   |     16      |    11       |      10
Item5 |   23   |     40      |    14       |      12
Item6 |   83   |     70      |    11       |      40
Item7 |   22   |     11      |    41       |      20

我想找到的唯一3行,參數1的總和最接近30
參數 2 的總和最接近60 參數 3 的總和最接近70

請注意這是一個帶有示例值的示例表

這是一種家庭作業問題,我已經花了很多時間試圖解決它。 我知道這是一個優化問題,主要是背包問題的邊緣情況,我應該使用動態編程來解決它,但是我無法弄清楚如何針對多個約束而不是一個約束來做到這一點。 我已經研究過多維背包,但不知道如何去做。

一個解釋如何做的 Jupyter notebook 會有很大幫助

你說的是背包問題,但有一些特點:

  • 你不想找到一個精確的總和,而是最接近一個值的結果;
  • 問題是多維的;
  • 該數字不能保證為正數;
  • 你沒有提供距離。

我認為最好的辦法是枚舉大小為K的子集並選擇最接近的總和。 這是蠻力,但動態規划可能有助於輸出子集並計算總和。

正如評論中指出的那樣,您首先必須定義closest含義。 也就是說,定義一個距離。 例如,歐幾里得距離很常見:

def d(p1, p2, p3):
    return p1*p1 + p2*p2 + p3*p3

讓我們從文件中提取數據,更准確地說,是最后三個值(參數 1、2、3)和行的索引:

DATA = """Label | Weight | Parameter 1 | Parameter 2 | Parameter 3
Item1 |   12   |     13      |    91       |      24
Item2 |   76   |     12      |    10       |      14
Item3 |   43   |     11      |    34       |      35
Item4 |   23   |     16      |    11       |      10
Item5 |   23   |     40      |    14       |      12
Item6 |   83   |     70      |    11       |      40
Item7 |   22   |     11      |    41       |      20"""

import io
import csv

f = io.StringIO(DATA)
reader = csv.reader(f, delimiter='|')
next(reader) # skip the header

L = [tuple([int(v) for v in row[-3:]] + [i]) for i, row in enumerate(reader)]
# [(13, 91, 24, 0), (12, 10, 14, 1), (11, 34, 35, 2), (16, 11, 10, 3), (40, 14, 12, 4), (70, 11, 40, 5), (11, 41, 20, 6)]

現在,設置行數K和目標T (三元組)

N = len(L)
K = 3
T = (30, 60, 70)

這是動態規划,因此我們需要存儲中間結果。 list_by_triplet_by_k是嵌套字典的列表:

  • dict的索引是使用的行數(我們對K感興趣,但需要計算其他值)。
  • 外層dict的key是“參數1”的總和;
  • 第一個嵌套字典的鍵是“參數2”的總和;
  • 第二個嵌套字典的鍵是“參數3”的總和;
  • 該值是已用行的列表。

(我沒有使用 4 維數組,因為它會非常稀疏。)

一個小技巧:我用目標初始化list_by_triplet_by_k 如果我們有 0 行,我們在 -T。

list_by_triplet_by_k = [{} for _ in range(N)]
list_by_triplet_by_k[0] = {-T[0]: {-T[1]: {-T[2]: [(-T[0], -T[1], -T[2], "target")]}}}

讓我們構建子集。 基本上,我們使用動態規划構建K+1棵樹的森林:

best = None
ret = []
for a, b, c, i in L:
    for k in range(0, K):
        list_by_triplet = list_by_triplet_by_k[k]
        for u in list_by_triplet.keys():
            for v in list_by_triplet[u].keys():
                for w in list_by_triplet[u][v]:
                    if (a, b, c, i) not in list_by_triplet[u][v][w]: # 0/1
                        list_by_triplet_by_k[k+1].setdefault(a+u, {}).setdefault(b+v, {})[c+w] = list_by_triplet[u][v][w] + [(a, b, c, i)]

    # compute the best match on the fly at the end (not a very useful optimization, but why not?):
    list_by_triplet = list_by_triplet_by_k[K-1]
    for u in list_by_triplet.keys():
        for v in list_by_triplet[u].keys():
            for w in list_by_triplet[u][v]:
                if (a, b, c, i) not in list_by_triplet[u][v][w]: # 0/1
                    cur = d(u+a, v+b, w+c)
                    if best is None or cur < best:
                        best = cur
                        ret = list_by_triplet[u][v][w] + [(a, b, c, i)]

可能有一個技巧可以通過設計避免重復,我不知道:我只是測試了該元素是否不在列表中。

結果:

print (best, ret)
# 227 [(-30, -60, -70, 'target'), (12, 10, 14, 1), (11, 34, 35, 2), (16, 11, 10, 3)]

評論:

暫無
暫無

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

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