Группируйте числа С++

Здесь проблема:

У вас есть N (N - число номеров, которые у вас есть). Разделите их на 2 группы таким образом, чтобы разница между суммами чисел в группах была минимальной.

Примеры:

5 // N

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

Разница равна 0, если мы поместим 1, 9 и 3 в группу A и 5 и 8 в группу B.

Сначала я должен вычислить сумму всех чисел и разделить ее на 2. Затем проверить всю возможную комбинацию чисел, сумма которых не превышает половину суммы всех чисел. После этого я выберу самое большое число и распечатаю группы.

У меня проблема с просмотром всех комбинаций, особенно если N - большие числа. Как я могу запускать все комбинации?


Также я думаю немного по-другому, я буду группировать числа в порядке убывания, и я положу наибольшее число в группе А и самый низкий в группе B. Тогда я делаю наоборот. Это работает с некоторыми номерами, но иногда оно не показывает оптимальную группировку. Например:

Если я использую предыдущий пример. Расположите номер в порядке убывания.

9, 8, 5, 3, 1.

Поместите самые большие в группе А и самые низкие в группе В.

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;
}

Ответ 1

Используя ограничение только двух групп, проблема zour уже решена, если вы можете найти одну группу чисел, сумма которых точно равна половине общего числа. таким образом, я предлагаю вам попробовать найти эту группу и, очевидно, поместить остальную часть в другую группу.

Предположение о том, что наибольшее количество в первой группе является простым. Теперь все остальное сложнее.

Это просто в двоичной системе: считайте, что для каждого числа у вас бит. Бит beeing 1 сигнализирует, что число находится в группе A, в противном случае оно находится в группе B. Весь дистрибутив можно описать путем объединения этих битов. Это можно считать числом. SO, чтобы проверить все комбинации, которые вы должны пройти через все числа, и вычислить комбинацию.

код:

#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;
}

изменить. Для дальнейших (и лучших) решений для генерации всех возможностей проверьте этот тент. Вот ссылка на книга knuths, которую я нашел здесь

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). Таким образом, A 1, а B - 23,5.

Теперь, чтобы объяснить трюк, почему мне нужно только посмотреть на половину: если мы посмотрим на десятичную интерпретацию 3 (011 binary), мы получим для группы A 23,5 и для группы B 1. Если мы сравним это с примером для 4, мы заметим, что у нас одинаковые числа, сгруппированные, точно в противоположных именах групп. Поскольку это не имеет никакого значения для вашей проблемы, мы не должны смотреть на это.

Edit3: Я добавил реальный код, чтобы попробовать, в псевдокоде я сделал неправильное предположение, что я всегда буду включать первый элемент в сумму, что было неправильно. Что касается вашего кода, на котором я начал: вы не можете выделять такие массивы. Другим решением вместо массива будет vector<int>, который избегает проблем передачи массива в функции. Использование этого было бы большим улучшением. Кроме того, этот код далек от хорошего. Вы столкнетесь с проблемами с int size (обычно это должно работать до 32 элементов). Вы можете работать над этим (возможно, как обращаться с произвольно большими целыми числами). Или вы на самом деле читаете на knuth (см. Выше). Я уверен, вы найдете какой-то рекурсивный подход. Этот код также медленный, так как он всегда восстанавливает всю сумму. Одна из оптимизаций заключалась бы в том, чтобы взглянуть на серые коды (я думаю, Кнут описывает их также). Таким образом, вам нужно только добавить/вычесть одно число на каждую перестановку, которую вы тестируете. Это будет повышение производительности в порядке n, так как вы заменяете n-1 дополнениями с добавлением/вычитанием 1.

Ответ 2

Хотя это, как прокомментировали другие, является проблемой NP-Complete, вы предоставили две довольно полезные оценки: вы хотите разделить группу чисел на две группы, и вы хотите получить суммы этих двух групп как как можно ближе.

Ваше предложение обдумывать общую сумму чисел и делить их на две - это правильная начальная точка - это означает, что вы знаете, какова идеальная сумма каждой группы. Я также подозреваю, что ваш лучший выбор - начать с того, что поместил наибольшее число, скажем, в группу A. (он должен попасть в одну группу, а худший - позже, так почему бы не поместить его туда?)

Это когда мы переходим к эвристике, которую вы прокручиваете до тех пор, пока группы не будут выполнены:

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 также может быть упорядочен путем проверки (по отношению к обеим "группам до сих пор" ) от наибольшего оставшегося до тех пор, пока "группа до сих пор", добавленная к проверяемому числу, больше 1.0 ниже t - после этого условие не может быть выполнено.)

Дайте мне знать, если это сработает!

Ответ 3

Если среднее значение отдельной группы совпадает с средним значением полного набора, то, очевидно, разница между двумя группами будет меньше. Используя это, мы можем выработать алгоритм для этой проблемы.

  • Получите среднее значение полного набора.
  • Возьмите наибольшее значение в наборе и поместите его в одну из групп.
  • Получите разницу между средним значением отдельной группы и средним значением для всего набора.
  • Поместите следующее наибольшее число в группе, которая имеет максимальную разницу.
  • Повторяйте шаги 3 и 4 до тех пор, пока все номера не будут помещены.

Это будет эффективный подход к получению почти оптимального решения.

Ответ 4

Как насчет этого:

  • Сортировка списка номеров.
  • Поместите наибольшее число в группу A. Удалите это число из списка.
  • Если сумма всех чисел в группе A меньше суммы всех чисел в группе B, goto 2.
  • Поместите наибольшее число в группу B. Удалите это число из списка.
  • Если сумма всех чисел в группе B меньше суммы всех чисел в группе A, goo 4.
  • Если в списке осталось больше нуля, перейдите в 2.