11

問題は次のとおりです。

あなたは N 個の数字を持っています (N はあなたが持っている数字の数を表します)。グループ内の数値の合計の差が最小限になるように、それらを 2 つのグループに分けます。

例:

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 に最大のものを、グループ B に最小のものを入れます。

Group A: 9
Group B: 1

他の方法。

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

等々。最終的に数字が 1 つしかない場合は、それを合計の低いグループに入れます。だから私は最終的に取得します:

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

差が 2 であるため、これは最適なグループ化ではありませんが、先ほど示したように、別の方法でグループ化すると、差が 0 になる可能性があります。

最適なグループ化を行うにはどうすればよいですか?

コード:

#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

4 に答える 4

2

他の人がコメントしているように、これはNP完全問題ですが、2つのかなり役立つ境界を提供しました.数値のグループを2つのグループに分割したいだけで、2つのグループの合計をできるだけ近づけたい. .

数値の総和を計算し、それを 2 で割るというあなたの提案は正しい出発点です。これは、各グループの理想的な合計が何であるかを知っていることを意味します。また、あなたの最善の策は、たとえばグループ A に最大数を入れることから始めることだと思います (それは 1 つのグループに入れなければならず、後で配置するのは最悪のグループになるので、そこに入れてみませんか?)

これは、グループが完了するまで循環するヒューリスティックに入るときです。

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 以上低くなるまで - その後、条件は満たされません。 .)

これがうまくいくかどうか教えてください!

于 2013-03-06T18:34:14.213 に答える
1

2つのグループのみの制約を使用すると、合計が合計の正確に半分になる数値の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;
}

編集:すべての可能性を生成するためのさらなる(そしてより良い)解決策については、このawnserをチェックしてください。ここに私が見つけたknuths本へのリンクがあります

edit2:要求に応じて列挙の概念を説明するには:

3つの要素があるとし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

ここで、最初の番号がグループにあり、2番目と3番目の番号がグループにないことを示す4このマップの番号を瞬時に取得すると(これは、グループBにあることを意味します)。したがって、はです。100AA1B23,5

ここで、半分だけを見る必要がある理由を説明します。10進数の解釈3011バイナリ)を見ると、GroupA 23,5とGroupが得られB 1ます。これを例と比較すると4、まったく反対のグループ名で同じ番号がグループ化されていることがわかります。これはあなたの問題に違いがないので、これを見る必要はありません。

edit3:試してみるために実際のコードを追加しました。擬似コードで、常に合計の最初の要素を含めるという誤った仮定をしましたが、これは間違っていました。私が始めたコードに関しては、そのような配列を割り当てることはできません。配列の代わりの別の解決策は、配列vector<int>サイズを関数に渡さなければならないという問題を回避することです。これを使用すると、大きな改善になります。さらに、このコードは決して良いものではありません。で問題が発生しますint size(通常、これは最大32個の要素で機能するはずです)。ただし、これを回避することはできます(これを試してみてください多分任意の大きな整数を処理する方法)。または、実際にknuthを読んでください(上記を参照)。再帰的なアプローチが見つかると確信しています。このコードも、常に合計を再構築するため、低速です。最適化の1つは、グレイコードを調べることです(Knuthもグレイコードについて説明していると思います)。そうすれば、テストする順列ごとに1つの数値を加算/減算するだけで済みます。加算を加算/減算nに置き換えるので、これは、の順序でパフォーマンスが向上します。n-11

于 2013-03-08T11:33:18.170 に答える
0

これはどう:

  1. 数字のリストを並べ替えます。
  2. グループ A に最大の数を入れます。その数をリストから削除します。
  3. グループ A のすべての数値の合計がグループ B のすべての数値の合計より小さい場合は、2 に進みます。
  4. グループ B に最大の数を入れます。その数をリストから削除します。
  5. グループ B のすべての数字の合計がグループ A のすべての数字の合計より小さい場合は、4 に進みます。
  6. リストに 0 個以上の数字が残っている場合は、2 に進みます。
于 2013-03-08T18:53:28.340 に答える
0

個々のグループの平均値が完全なセットの平均値と同じである場合、明らかに 2 つのグループの差は小さくなります。これを使用することで、この問題のアルゴリズムを導き出すことができます。

  1. 完全なセットの平均を取得します。
  2. セット内の最大値を取り、グループの 1 つに入れます。
  3. 個々のグループの平均とセット全体の平均の差を取得します。
  4. 最大の差を持っているグループで次に大きい数字を配置します。
  5. すべての数字が配置されるまで、手順 3 と 4 を繰り返します。

これは、最適に近い解を得るための効率的なアプローチです。

于 2013-03-06T22:15:19.840 に答える