将数字分组C ++

时间:2013-03-01 00:09:51

标签: c++ algorithm numbers combinations

问题在于:

您有N(N代表您拥有的数字)数字。将它们分成两组,使得组中数字之和的差异最小。

示例:

5 // N

1, 9, 5, 3, 8 // The numbers

如果我们在A组中放置1,9和3,在B组中放置5和8,则差异为0.

我认为首先我应该计算所有数字的总和并将其除以2.然后检查可能的数字组合,其总和不高于所有数字总和的一半。在我这样做之后,我将选择最大的数字并打印出组。

我遇到所有组合的问题,特别是当N是大数字时。我怎样才能完成所有组合?


另外我认为有点不同,我会按降序对数字进行分组,我会将最大数字放在A组中,最低数字放在B组中。然后我会反过来做。这适用于某些数字,但有时它不会显示最佳分组。例如:

如果我使用前面的例子。按降序排列数字。

9, 8, 5, 3, 1.

将A组放在A组,将最低组放在B组。

Group A: 9
Group B: 1

其他方式。

Group A: 9, 3
Group B: 1, 8

等等。如果最后我只有一个数字,我会把它放在总和较低的组中。 所以我终于得到了:

Group A: 9, 3
Group B: 1, 8, 5

这不是最佳分组,因为差异是2,但如我所示,以不同的方式分组,差异可以是0。

如何获得最佳分组?

CODE:

#include <iostream>
#include <cmath>
#include <string>
using namespace std;
int convertToBinary(int number) {
    int remainder;
    int binNumber = 0;
    int i = 1;
    while(number!=0)
    {
        remainder=number%2;
        binNumber=binNumber + (i*remainder);
        number=number/2;
        i=i*10;
    }
    return binNumber;
}
int main()
{
    int number, combinations, sum = 0;
    double average;
    cin >> number;
    int numbers[number];
    for(int i = 0; i<number; i++)
    {
        cin >> numbers[i];
        sum += numbers[i];
    }
    if(sum%2 == 0)
    {
        average = sum/2;
    }
    else
    {
        average = sum/2 + 0.5;
    }
    combinations = pow(2,number-1);
    double closest = average;
    for(int i = 0; i<=combinations;i++)
    {
        int rem;
        int temp_sum = 0;
        int state = convertToBinary(i);
        for(int j = 0; state!=0; j++)
        {
            int rem =state%10;
            state = state/10;
            if(rem == 1)
            {
                temp_sum = temp_sum + numbers[j];
            }
        }
        if(abs(average-temp_sum)<closest)
        {
            closest = abs(average-temp_sum);
            if(closest == 0)
            {
                break;
            }
        }
    }
    cout << closest*2;
    return 0;
}

4 个答案:

答案 0 :(得分:2)

尽管正如其他人所评论的那样,这是一个NP-Complete问题,但您提供了两个相当有用的界限:您只想将数字组分成两组,并且您希望将两组的总和作为尽可能接近。

您建议计算出数字的总和并将其除以2是正确的起点 - 这意味着您知道每个组的理想总和是多少。我还怀疑你最好的选择是把最大的数字放在A组中。(它必须进入一组,这是最差的一组,所以为什么不把它放在那里?)< / p>

这是我们进入启发式循环的过程,直到小组完成为止:

N: Size of list of numbers.
t: sum of numbers divided by two (t is for target)

1. Is there a non-placed number which gets either group to within 0.5 of t? If so, put it in that group, put the remaining numbers in the other group and you're done.
2. If not, place the biggest remaining number in the group with the current lowest sum
3. go back to 1.

毫无疑问会出现失败的情况,但作为一种粗略的方法,这应该经常接近。要实际编写上述代码,您需要将数字放在有序列表中,以便从最大到最小的方式轻松完成。 (然后,步骤1也可以通过检查(对于两个“目前为止的组”)从最大剩余时间直到“被检查的组”添加到被检查的数字超过t以下的1.0之后进行简化 - 之后不能满足条件。)

请告诉我这是否有效!

答案 1 :(得分:1)

如果你能找到一组数字,其总和是完全的一半,那么只使用两组的约束zour问题就已经解决了。因此,我建议你试着找到这个小组,然后将其余部分放到另一组中。

将最大数字放在第一组中的假设很简单。现在其余的都比较棘手。

这在二进制系统中很简单:考虑每个数字,你有一点。位1表示该数字在组A中,否则表示在组B中。整个分布可以通过这些位的连接来描述。这可以被认为是一个数字。所以要检查所有组合,你必须通过所有数字并计算组合。

代码:

#include <iostream>
#include <memory>
using namespace std;

int partition(const std::unique_ptr<int[]>& numbers, int elements) {
    int sum = 0;

    for(int i=0; i<elements; ++i) {
        sum += numbers[i];
    }

    double average = sum/2.0;
    double closest = average+.5;

    int beststate = 0;


    for(int state=1; state< 1<<(elements-1);++state) { 
        int tempsum = 0;
        for(int i=0; i<elements; ++i) {
            if( state&(1<<i) ) {
                tempsum += numbers[i];
            }
        }

        double delta=abs(tempsum-average);
        if(delta < 1) { //if delta is .5 it won't get better i.e. (3,5) (9) => average =8.5
            cout << state;
            return state;
        }

        if(delta<closest) {
            closest   = delta;
            beststate = state;
        }
    }

    return beststate;
}

void printPartition(int state, const std::unique_ptr<int[]>& numbers, int elements) {
    cout << "(";
    for(int i=0; i<elements; ++i) {
        if(state&(1<<i)) {
            cout << numbers[i]<< ",";
        }
    }
    cout << ")" << endl;
}

int main()
{
    int elements;

    cout << "number of elements:";
    cin >> elements;

    std::unique_ptr<int[]> numbers(new int[elements]);

    for(int i = 0; i<elements; i++)
    {
        cin >> numbers[i];
    }

    int groupA = partition(numbers, elements);

cout << "\n\nSolution:\n";
    printPartition(groupA, numbers, elements);
    printPartition(~groupA,numbers, elements);
    return 0;
}

编辑:有关生成所有可能性的更多(和更好)解决方案,请检查this awnser。这是knuths book的链接,我找到了here

edit2 :按要求解释枚举的概念:

假设我们有三个元素1,23,5。通过填写表格可以生成忽略排列的所有可能组合:

1 | 23 | 5         Concatination   Decimal interpretation
-----------
0 |  0 | 0         000                0
0 |  0 | 1         001                1
0 |  1 | 0         010                2
0 |  1 | 1         011                3
1 |  0 | 0         100                4
1 |  0 | 1         101                5
1 |  1 | 0         110                6
1 |  1 | 1         111                7

如果我们现在立即采用4号码,则会映射到100,表示第一个号码在群组A中,而第二个和第三个号码不在({暗示他们属于B组)。因此A1B23,5

现在解释为什么我只需要看一半:如果我们看一下十进制解释3011二进制),我们得到的是组A {{1} }和小组23,5 B。如果我们将此与1的示例进行比较,我们会注意到我们将相同的数字分组,只是在完全相反的组名中。由于这对你的问题没有任何影响,我们不必看这个。

<强> EDIT3 : 我添加了真实的代码来尝试,在伪代码中,我做了错误的假设,我将总是包括总和中的第一个元素,这是错误的。至于我开始的代码:你不能像这样分配数组。另一个解决方案而不是数组将是4,它避免了必须将数组大小传递给函数的问题。使用它将是一个很大的改进。此外,这段代码远非好。您将遇到vector<int>的问题(通常这应该适用于最多32个元素)。你可以在这周围工作(尝试这可能是How to handle arbitrarily large integers)。或者您实际上阅读了knuth(见上文)我相信您会找到一些递归方法。 这段代码也很慢,因为它总是重建整个总和。一个优化是调查gray codes(我认为Knuth描述了它们)。这样,您只需要为每个测试的排列添加/减去一个数字。这将是int size的性能提升,因为您将n添加替换为n-1加/减。

答案 2 :(得分:0)

如果个体组的平均值与完整集的平均值相同,那么两组之间的差异显然会更小。通过使用这个,我们可以为这个问题提出一个算法。

  1. 获得整套的平均值。
  2. 取集合中的最大值并将其放入其中一个。
  3. 获取单个组的平均值与整个组的平均值的差异。
  4. 将下一个最大数字放在具有最大差异的组中。
  5. 重复步骤3和4,直到放下所有号码。
  6. 这将是获得近乎最佳解决方案的有效方法。

答案 3 :(得分:0)

这个怎么样:

  1. 对数字列表进行排序。
  2. 将最大的数字放在A组中。从列表中删除该号码。
  3. 如果A组中所有数字的总和小于B组中所有数字的总和,请转到2.
  4. 将最大的数字放在B组中。从列表中删除该号码。
  5. 如果B组中所有数字的总和小于A组中所有数字的总和,请转到4.
  6. 如果列表中剩余的数字超过零,请转到2。