Алгоритм справедливого распределения чисел на два набора - PullRequest
3 голосов
/ 02 октября 2009

Учитывая набор из n чисел (1 <= n <= 100), где каждое число является целым числом от 1 до 450, нам нужно распределить этот набор чисел на два набора A и B, чтобы в следующих двух случаях верно: </p>

  1. Общее количество в каждом наборе отличается максимум на 1.
  2. Сумма всех чисел в A максимально равна сумме всех чисел в B, т.е. распределение должно быть справедливым.

Может кто-нибудь предложить эффективный алгоритм для решения вышеуказанной проблемы?

Спасибо.

Ответы [ 13 ]

9 голосов
/ 02 октября 2009

Так как числа маленькие, это не NP-полная.

Для ее решения вы можете использовать динамическое программирование:

Составить трехмерную таблицу логических значений где true при t [s, n, i] означает, что сумма s может быть достигнута с помощью подмножества из n элементов ниже индекса i. Чтобы вычислить значение для t [s, n, i], проверьте t [s, n, i-1] и t [s - a [i], n-1, i-1]. Затем посмотрите в таблице на второй индекс n / 2, чтобы найти лучшее решение.

Редактировать: На самом деле вам не нужна полная таблица сразу. Вы можете создать двумерную таблицу t_i [s, n] для каждого индекса i и вычислить таблицу для i из таблицы для i-1, поэтому вам понадобятся только две из этих двумерных таблиц, что экономит много памяти. (Спасибо Мартину Хоку.)

3 голосов
/ 02 октября 2009

Это ограниченная версия проблемы разбиения чисел . Обычно цель состоит в том, чтобы найти любые 2 непересекающихся подмножества, которые минимизируют разницу сумм. Ваша проблема ограничена в том смысле, что вы рассматриваете только 1 возможность: 2 набора размера N / 2 (или 1 набор N / 2 и один набор N / 2 + 1, если общее число неравномерно). Это значительно сокращает пространство поиска, но я не могу найти хороший алгоритм на данный момент, я подумаю об этом.

2 голосов
/ 02 октября 2009

У меня есть алгоритм для вас. Он использует много рекурсивных и итеративных концепций.

Предполагая, что у вас есть n чисел Xn с 1 <= n <= 100 и 1 <= Xn <= 450. </p>

  1. Если n <3, то распределить числа и остановить алгоритм, </p>

  2. Если n> 2, то отсортируйте список чисел в порядке возрастания,

  3. Рассчитать общую сумму S всех чисел,
  4. Затем разделите предыдущий итог S на (n - n% 2) / 2 и получите значение A,
  5. Теперь мы создадим пару чисел, сложение которых будет как можно ближе к А. Получить первое число и найти второе число, чтобы получить сумму S1 , максимально приближенную к А. Поместите S1 в новый список чисел и сохраните в памяти, как сумма была вычислена, чтобы иметь базовые числа позже.
  6. Выполняйте 5. пока числа в списке не станут <2. Затем поместите оставшиеся числа в список сумм и перезапустите алгоритм, чтобы указать 1. с новым списком. </li>

Пример:

Предполагается: n = 7 и числа 10, 75, 30, 45, 25, 15, 20

Пропуск 1:

Так как n> 2, отсортируйте список: 10, 15, 20, 25, 30, 45, 75

Сумма S = 220

A = 220 / ((7-1) / 2) = 73

Пара:

10 & 75 => 85

15 & 45 => 60

20 и 30 => 50

Остальные числа <2, поэтому добавьте 25 в итоговый список: 85 (10,75), 60 (15,45), 50 (20,30), 25 (25) </p>

Пропуск 2:

n = 4 и числа 85, 60, 50, 25

Количество списков> 2, так что список сортировки: 25 (25), 50 (20,30), 60 (15,45), 85 (10,75)

Сумма S все та же (S = 220), но A необходимо пересчитать: A = 220 / ((4-0) / 2) = 110

Пара:

25 и 85 => 110

50 & 60 => 110

Список сумм: 110 (25 (25), 85 (10,75)), 110 (50 (20,30), 60 (15,45))

Пропуск 3:

n = 2 и цифры 110, 110

n <3, так что распределите числа: </p>

А = 25, 10, 75

B = 20, 30, 15, 45

Это работает для каждого сценария, который я тестировал.

2 голосов
/ 02 октября 2009

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

Как только вы сможете решить ее, вы можете добавить еще одно состояние к вашему DP - количество людей, уже выбранных для подмножества. Это дает вам алгоритм N ^ 3.

2 голосов
/ 02 октября 2009

Неважно , я думал, что числа были последовательными. Это выглядит как проблема с рюкзаком , то есть NP hard.


Числа последовательные?

  1. Поместите наибольшее число в A
  2. Поместите следующий по величине номер в B
  3. Поместите следующий по величине номер в B
  4. Поставьте следующий по величине номер в A
  5. Повторяйте шаг 1, пока не будут назначены все номера.

Доказательство:

После назначения каждого кратного 4 числа, A и B содержат одинаковое количество элементов, а сумма элементов в каждой группе одинакова, поскольку

(n) + (n - 3) == (n - 1) + (n - 2)

В последней итерации мы находимся на шаге 1 выше, и у нас осталось 0, 1 1 , 2 [1,2] или 3 [1,2,3] числа.

В случае 0 мы закончили и группы равны по количеству и весу.

В случае 1 мы присваиваем число 1 группе А. В группе А есть еще один элемент и еще один вес. Это настолько справедливо, насколько мы можем оказаться в этой ситуации.

В случае 2 мы присваиваем число 2 группе A и число 1 группе B. Теперь группы имеют одинаковое количество предметов, а группа A имеет один дополнительный вес. Опять же, это настолько справедливо, насколько мы можем получить.

В случае 3 присвойте число 3 группе A и присвойте номера 2 и 1 группе B. Теперь группы имеют одинаковый вес (3 == 2 + 1), а в группе B есть один дополнительный элемент.

2 голосов
/ 02 октября 2009

Если числа последовательные, вы просто чередуете их, назначая их между A и B.

Я подозреваю, что это не так, в этом случае ...

Назначьте наибольшее неназначенное число группе с наименьшей суммой, если только различие в размере групп не меньше или равно количеству неназначенных номеров (в этом случае все оставшиеся номера присваиваются меньшей группе). 1005 *

Он не найдет лучшего решения во всех случаях, но он близок и прост.

1 голос
/ 02 октября 2009

@ ShreevatsaR отмечает, что приведенный ниже алгоритм известен как жадный алгоритм . Это не очень хорошо с определенными входами (я попробовал 10 различных наборов случайно сгенерированных наборов входов размера 100, и во всех случаях суммы были очень близки, что привело меня к мысли, что сортировки входных данных было достаточно для успеха этого алгоритма ).

См. Также «Самая простая трудная проблема», американский ученый, март-апрель 2002 г. , рекомендовано Шриеваца .

#!/usr/bin/perl

use strict;
use warnings;

use List::Util qw( sum );

my @numbers = generate_list();

print "@numbers\n\n";

my (@A, @B);
my $N = @numbers;

while ( @numbers ) {
    my $n = pop @numbers;
    printf "Step: %d\n", $N - @numbers;
    {
        no warnings 'uninitialized';

        if ( sum(@A) < sum(@B) ) {
            push @A, $n;
        }
        else {
            push @B, $n;
        }
        printf "A: %s\n\tsum: %d\n\tnum elements: %d\n",
            "@A", sum(@A), scalar @A;
        printf "B: %s\n\tsum: %d\n\tnum elements: %d\n\n",
            "@B", sum(@B), scalar @B;
    }
}

sub generate_list { grep { rand > 0.8 } 1 .. 450 }

Обратите внимание, что generate_list возвращает список в порядке возрастания.

1 голос
/ 02 октября 2009

Ваше требование в # 2 нуждается в разъяснении, потому что: «Сумма всех чисел в A максимально близка к сумме всех чисел в B», ясно, но тогда ваше утверждение «распределение должно быть справедливым» делает все неясным Что именно означает «справедливый»? Нужен ли процессу случайный элемент?

0 голосов
/ 02 октября 2009

Если вам нужен идеальный ответ, вы должны создать и просмотреть все возможные наборы ответов. Если вам нужен достаточно хороший ответ, то стоит использовать метод имитации отжига. Вот некоторый C-код, который использует очень примитивный график охлаждения, чтобы найти ответ.

#include <stdio.h>
#include <stdlib.h>

#define MAXPAR 50
#define MAXTRIES 10000000

int data1[] = {192,130,446,328,40,174,218,31,59,234,26,365,253,11,198,98,
               279,6,276,72,219,15,192,289,289,191,244,62,443,431,363,10
              } ;
int data2[] = { 1,2,3,4,5,6,7,8,9 } ;

// What does the set sum to
int sumSet ( int data[], int len )
{
    int result = 0 ;
    for ( int i=0; i < len; ++i )
        result += data[i] ;
    return result ;
}

// Print out a set
void printSet ( int data[], int len )
{
    for ( int i=0; i < len; ++i )
        printf ( "%d ", data[i] ) ;
    printf ( " Sums to %d\n", sumSet ( data,len ) ) ;
}

// Partition the values using simulated annealing
void partition ( int data[], size_t len )
{
    int set1[MAXPAR] = {0} ;    // Parttition 1
    int set2[MAXPAR] = {0} ;    // Parttition 2
    int set1Pos, set2Pos, dataPos, set1Len, set2Len ;  // Data about the partitions
    int minDiff ; // The best solution found so far
    int sum1, sum2, diff ;
    int tries = MAXTRIES ; // Don't loop for ever

    set1Len = set2Len = -1 ;
    dataPos = 0 ;

    // Initialize the two partitions
    while ( dataPos < len )
    {
        set1[++set1Len] = data[dataPos++] ;
        if ( dataPos < len )
            set2[++set2Len] = data[dataPos++] ;
    }


    // Very primitive simulated annealing solution
    sum1 = sumSet ( set1, set1Len ) ;
    sum2 = sumSet ( set2, set2Len ) ;
    diff = sum1 - sum2 ;    // The initial difference - we want to minimize this
    minDiff = sum1 + sum2 ;
    printf ( "Initial diff is %d\n", diff ) ;

    // Loop until a solution is found or all are tries are exhausted
    while ( diff != 0 && tries > 0 )
    {
        // Look for swaps that improves the difference
        int newDiff, newSum1, newSum2 ;
        set1Pos = rand() % set1Len ;
        set2Pos = rand() % set2Len ;

        newSum1 = sum1 - set1[set1Pos] + set2[set2Pos] ;
        newSum2 = sum2 + set1[set1Pos] - set2[set2Pos] ;
        newDiff = newSum1 - newSum2 ;
        if ( abs ( newDiff ) < abs ( diff ) ||      // Is this a better solution?
                tries/100 > rand() % MAXTRIES )     // Or shall we just swap anyway - chance of swap decreases as tries reduces
        {
            int tmp = set1[set1Pos] ;
            set1[set1Pos] = set2[set2Pos] ;
            set2[set2Pos] = tmp ;
            diff = newDiff ;
            sum1 = newSum1 ;
            sum2 = newSum2 ;

            // Print it out if its the best we have seen so far
            if ( abs ( diff ) < abs ( minDiff ) )
            {
                minDiff = diff ;
                printSet ( set1, set1Len ) ;
                printSet ( set2, set2Len ) ;
                printf ( "diff of %d\n\n", abs ( diff ) ) ;
            }
        }


        --tries ;
    }

    printf ( "done\n" ) ;
}


int main ( int argc, char **argv )
{
    // Change this to init rand from the clock say if you don't want the same
    // results repoduced evert time!
    srand ( 12345 ) ;
    partition ( data1, 31 ) ;
    partition ( data2, 9 ) ;
    return 0;
}
0 голосов
/ 02 октября 2009

Имитация отжига может довольно быстро найти лучшие и лучшие ответы. Вы можете сохранить 1. true, улучшая близость 2.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...