将列表分成两部分,它们的总和彼此最接近

时间:2010-12-18 18:42:52

标签: algorithm dynamic-programming knapsack-problem partition-problem

这是算法问题:

将列表分成两部分(总和),表示它们的总和最接近(大多数)

列表长度为1&lt; = n&lt; = 100并且它们的(数字)权重1 <= w <= 250,在问题中给出。

例如:23 65 134 32 95 123 34

1.sum = 256

2.sum = 250

1.list = 1 2 3 7

2.list = 4 5 6

我有一个算法,但它并不适用于所有输入。

  1. 初始化。列表list1 = [],list2 = []
  2. 排序元素(给定列表)[23 32 34 65 95 123 134]
  3. 弹出最后一个(最多一个)
  4. 插入不同的列表
  5. 实施: list1 = [],list2 = []

    1. 选择134 insert list1。 list1 = [134]
    2. 选择123插入列表2。因为如果你插入到list1,差异越来越大.3。选择95并插入list2。因为sum(list2)+ 95 - sum(list1)较少。
    3. 依旧......

5 个答案:

答案 0 :(得分:7)

您可以将其重新表述为knapsack problem

您有一个总重量为M的物品清单,应安装在可容纳最大重量M / 2的垃圾箱中。包装在垃圾箱中的物品应尽可能重,但不要超过垃圾箱。

对于所有权重都是非负数的情况,此问题仅为weakly NP-complete并且具有多项式时间解决方案。

可以在Wikipedia上找到针对此问题的动态编程解决方案的说明。

答案 1 :(得分:4)

问题是NPC,但是有一个伪多项式算法,这是一个2-Partition问题,你可以按照sub set sum问题的伪多项式时间算法来解决这个问题。如果输入大小与输入值多项相关,则可以在多项式时间内完成。

在你的情况下(权重<250),它是多项式(因为权重&lt; = 250 n =&gt; sums&lt; = 250 n ^ 2)。

设Sum =权重之和,我们必须创建二维数组A,然后按列构造A,

A [i,j] =真如果(j == weight [i]或j - weight [i] = weight [k](k在列表中))。

使用此算法创建数组需要O(n ^ 2 * sum / 2)。

最后我们应该找到最有价值的专栏。

以下是一个例子:

项:{0,1,2,3} 权重:{4,7,2,8} =&gt; sum = 21 sum / 2 = 10

items/weights 0|  1  | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10    
  --------------------------------------------------------- 
  |0             |  0  | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0
  |1             |  0  | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0
  |2             |  0  | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1
  |3             |  0  | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1

因为a [10,2] == true,分区是10,11

这是我找到的算法here并进行了一些编辑以解决您的问题:

bool partition( vector< int > C ) {
 // compute the total sum
 int n = C.size();
 int N = 0;
 for( int i = 0; i < n; i++ ) N += C[i];
 // initialize the table 
 T[0] = true;
 for( int i = 1; i <= N; i++ ) T[i] = false;
 // process the numbers one by one
 for( int i = 0; i < n; i++ )
  for( int j = N - C[i]; j >= 0; j--)
   if( T[j] ) T[j + C[i]] = true;

 for(int i = N/2;i>=0;i--)
    if (T[i])
      return i;
 return 0;
}

我刚刚返回T [i],这是真的,而不是返回T [N / 2](最大到最小顺序)。

找到给出这个值的路径并不难。

答案 2 :(得分:2)

这个问题至少与NP完全问题subset sum一样困难。你的算法是一个贪婪的算法。这种类型的算法速度快,可以快速生成近似解,但无法找到NP完全问题的精确解。

蛮力方法可能是解决问题的最简单方法,但如果元素太多,它会慢下来。

  • 尝试将元素分成两组的所有可能方法,并计算总和中的绝对差值。
  • 选择绝对差异最小的分区。

生成所有分区可以通过考虑从0到2 ^ n的每个整数的二进制表示来完成,其中每个二进制数字确定相应的元素是在左分区还是右分区。

答案 3 :(得分:0)

试图解决我在以下想法中遇到的相同问题,这似乎是一个解决方案,但它在线性时间内起作用。能否提供一个示例来表明它不起作用或解释为什么它不是解决方案?

arr = [20,10,15,6,1,17,3,9,10,2,19] # a list of numbers

g1 = []
g2 = []

for el in reversed(sorted(arr)):
    if sum(g1) > sum(g2):
        g2.append(el)
    else:
        g1.append(el)

print(f"{sum(g1)}: {g1}")
print(f"{sum(g2)}: {g2}")

答案 4 :(得分:0)

打字稿代码:

import * as _ from 'lodash'
function partitionArray(numbers: number[]): {
    arr1: number[]
    arr2: number[]
    difference: number
} {
    let sortedArr: number[] = _.chain(numbers).without(0).sortBy((x) => x).value().reverse()
    let arr1: number[] = []
    let arr2: number[] = []
    let median = _.sum(sortedArr) / 2
    let sum = 0

    _.each(sortedArr, (n) => {
        let ns = sum + n
        if (ns > median) {
            arr1.push(n)
        } else {
            sum += n
            arr2.push(n)
        }
    })
    return {
        arr1: arr1,
        arr2: arr2,
        difference: Math.abs(_.sum(arr1) - _.sum(arr2))
    }
}