如何将一行数分成N组,使每组的总和最接近它们的平均值?

时间:2012-02-14 10:27:59

标签: algorithm list

我有以下问题:我将M个数字排成一行。我需要将该行划分为N个组,使得每个组的数量之和最接近这些总和的平均值。实际指标并不重要:我们可以选择最小化绝对差值或方差等的总和,具体取决于最简单的解决方案。

类似的问题是集合的划分,即NP Hard。但是,这里我们有额外的约束:组必须打包连续的数字,因此可能有一个不涉及强力搜索的解决方案。数字很​​大。

修改

示例:

数字:1 2 3 4 5 6 7 8 9 10,需要分为3组

假设我们想要最小化绝对差值之和(SAD)。

团体:(1)1 2 3 4 5 6(总和= 21); (2)7 8(总和= 15); (3)9 10(总和= 19)

平均值=(21 + 15 + 19)/ 3 = 18.33,SAD = 21-18.33 + 18.33-15 + 19-18.33 = 6.67 < - 这就是我们想要最小化的。

5 个答案:

答案 0 :(得分:2)

一旦你知道应该是什么,那么你可以创建接近这个总和的组。如果您的指标很好,那么您应该能够使用二进制搜索来查找实际总和。当您瞄准特定金额时,您可以通过列表向组添加数字,直到组总和超过总和大小。然后取或不取最后一个整数。通过整个列表执行此操作,并查看哪些组总和偏离总和最多。然后返回列表,尝试组合大小的偏差范围内的组合。它应该足够快。否则使用动态编程。

答案 1 :(得分:1)

按降序对数组进行排序 有三个数字存储金额 迭代循环并将当前数字添加到最小总和 答案是(10,5,4),(9,6,3),(8,7,2,1)

#include<iostream>
#include<stdio.h>
#include <algorithm>

using namespace std;
int maximum(int x, int y, int z) {
int max = x; /* assume x is the largest */

if (y > max) { /* if y is larger than max, assign y to max */
    max = y;
} /* end if */

if (z > max) { /* if z is larger than max, assign z to max */
    max = z;
} /* end if */

return max; /* max is the largest value */
} 

int main()
 {
int array[] = {1 ,2, 3, 4, 5, 6, 7, 8, 9, 10};
int size = sizeof(array)/sizeof(array[0]);
int part1=0;
int part2=0;
int part3=0;

sort(array,array+size,greater<int>());
for(int x=0;x<size;x++)
{
    if( part1 < part2 && part1 < part3)
    {
        part1 +=array[x];
    }else if(part2 < part3){
        part2 +=array[x];
    }else{
        part3 +=array[x];
    }
}

printf("first part1 = %d\n",part1 );
printf("first part2 = %d\n",part2 );
printf("first part3 = %d\n",part3 );

printf("-------------------------------\n");
printf("largest number = %d\n",maximum(part1,part2,part3));

}

答案 2 :(得分:0)

我想我得到了你的来源。作为程序员,我在数字序列中想到了它,我把快速的东西放在一起作为它的情人节,我要出去吃饭:)这是一个简单的版本:

a = all numbers added together
b = number of groups
m = a/b (value is mean)

c = array(a)DES (add all numbers to an array in decending order)

foreach c
    if((m-(c[0] + c[1])) < (m-(c[0]))
        if((m-(c[0] + c[1] + c[2])) < (m-(c[0] + c[1])))
        else
        g1 = c[0],c[1]
        c = c - (c[0],c[1])

    else
    g1 = c[0]
    c = c - c[0]

foreach c
    if((m-(c[0] + c[1])) < (m-(c[0]))
    else
    g2 = c[0]

我很快把它放在一起,所以它可能不准确,但希望你能看到序列和程序。当然,所有'c'值都将被动态选择,就像每个'foreach'循环一样。您可能需要在末尾处使用foreach语句来处理任何剩余的数字,并将它们添加到最接近均值的值。

情人节快乐!

答案 3 :(得分:0)

这是一个有效的(虽然没有经过全面测试)JavaScript解决方案。

它主要使用动态脚本来构建暴力堆叠的for循环(有序组合),以获取数组中每个组的起始索引。

var A = [1,2,3,4,5,6,7,8,9,10];
var G = 3;
function find(line, groups) {
    var length = line.length;
    var mean = line.sum() / groups;
    var temp = [0];
    var bestsad = 4294967295;
    var beststarts = [];
    var dynamic = "var x0 = 0; ";
    for(var i=1; i<groups; i++) {
        dynamic += "for(var x" + i + "=x" + (i-1) + "+1;x" + i + "<" + length + ";x" + i + "++) ";
        temp.push("x" + i);
    }
    dynamic += "{ var sad = getSAD(line, mean, [" + temp.join(",") + "]);";
    dynamic += "if(sad < bestsad) { bestsad = sad; beststarts = [" + temp.join(",") + "] ;} }"
    eval(dynamic);
    console.log("Best SAD " + bestsad);
    console.log("Best Start Indexes " + beststarts);
    return beststarts;
}
function getSAD(line, mean, starts) {
    var sums = [];
    var sad;
    for(var i = 0; i < starts.length-1; i++)
    {
        var idx = i;
        sums.push(line.slice(starts[idx], starts[i+1]).sum());
    }
    sums.push(line.slice(starts[starts.length-1]).sum());
    sad = sums.sad(mean);
    return sad;
}

Array.prototype.sum = function() {
    var result = 0;
    for(var i=0; i<this.length; i++)
        result += this[i];
    return result;
}
Array.prototype.sad = function(mean) {
    var result = 0;
    for(var i=0; i<this.length; i++)
        result += Math.abs(this[i] - mean);
    return result;
}
find(A, G);

以下是 var dynamic 变量/字符串保存/执行的脚本。

var x0 = 0; 
for(var x1=x0+1;x1<10;x1++) 
 for(var x2=x1+1;x2<10;x2++) { 
  var sad = getSAD(line, mean, [0,x1,x2]);
  if(sad < bestsad) { 
   bestsad = sad; 
   beststarts = [0,x1,x2] ;
  } 
}

为什么不使用组索引向量+递归?对于这种类型的递归问题,迭代方法是最优的。不可否认,动态脚本的开销(以及增加的复杂性)会抵消小数组的任何好处,但是当处理实际数据(大型数组)时,它会更快地生成答案。

答案 4 :(得分:0)

这是一个有趣的问题。我将使用你的例子将数字1..10分成三组来说明我的答案。该解决方案将适用于任何数字组和任意数量的组。当然,当数字集的大小很大时,您可能无法使用蛮力方法。话虽如此,大量的数字可以用类似的方式处理,但稍后会更多。

假设我们在集合中有M个连续数字,表示为(1..M),我们希望将它们分成N组。

要确定的第一件事是您将每组的总和进行比较的值。这只是一组数字除以组数N的总和。

在示例中,sumOf(1..M)= 55且N = 3,因此55/3 = 18.33是每个组应求和的值。您希望最小化组和之间的差异和18.33

作为另一个例子,如果你想将1到20的数字组分成两组,那么你需要最小化组和之间的差异和sumOf(1..20)= 210除以2组= 210/2 = 105。

下一步是找到所有可能的组。这是另一个有趣的问题,因为限制了包含连续数字的proups,组合的总数不会超出你的预期。

找到组合是一个递归问题,很容易计算出一般方程。

让我们从一个简单的案例开始。集合中10个数字的组合数(1..10)。那么,只有一组,数字(1..10)

现在,10个数字中有2组的组合数。答案是M-1或10-1 = 9,即

(1),(2..10)
(1..2) (3..10)
(1..3) (4..10)
(1..4) (5..10)
(1..5) (6..10)
(1..6) (7..10)
(1..7) (8..10)
(1..8) (9..10)
(1..9) (10)

因此,一组大小M具有M-1组的组合。这是递归的基础。

10个数字中3组的组合数。

嗯,第一组将是以下之一

(1),(1..2),(1..3) ,(1..4) ,(1..5),(1..6) ,(1..7) ,(1..8)  

鉴于其中任何一个作为第一组,让我们计算出其余数字中存在多少组的2组。

让三个中的第一组=(1)。我们剩下9个数字,知道这些可以使9-1 = 2组的8种不同组合 让三个中的第一组=(1..5)。我们剩下五个数字,这些可以使5-1 = 4个不同的2个数字组。

所以,总的来说我们会有

(1) -> 8 combinations
(1..2) -> 7 combinations
(1..3) -> 6 combinations
(1..4) -> 5 combinations
(1..5) -> 4 combinations
(1..6) -> 3 combinations
(1..7) -> 2 combinations
(1..8) -> 1 combinations

给予SumOf(1..8),或者一般(sum(1..M-2),组的组合.SumOf(1..8)= 8 * 9/2 = 36

因此,10个数字中有3组的36种组合,其中每组包含连续的数字。

另外,对于100个数字中的3个组,你有sumOf(1..98)= 98 * 99/2 = 4851组的组合,因此当M增加时你将得到更多的组合并且作为M的某个值蛮力方法可能无法实现。

上面概述的方法可用于设计一个简单的递归算法,以获得集合中所有组的组合(1..M)。

此外,可以针对一组M个数中的任意数量的N个组计算出简单的等式。例如,如果你移动到10个数字中的4个组,那么你有第一组的情况是(1..3),然后在剩下的7个数字中找到3组的组合。总和(1..M-2)=总和(1..5)等。

无论如何,回到问题。您拥有所有组的组合,因此您可以遍历组并计算每个组合的SAD,并选择最小化SAD的组合。

当组合的数量非常大并且您无法查看每个组合时,您可以尝试自动选择随机选择组或某种进化算法方法,其中您从一些随机选择的组合开始然后随机将数字从一个组移动到另一个组并保留那些具有最低SAD的组。继续此步骤,直到您看到SAD没有进一步改进。

或者你可能会像@Robert King建议的那样,从一个组合开始,并通过将数字从一个组移动到另一个组来改进它。