интеллектуальное создание комбинаций комбинаций

Допустим, у меня есть класс из 30 учеников, и я хочу сгенерировать все возможные способы, которыми их можно разделить на группы по 5 человек (порядок не имеет значения).

Я знаю, как найти все комбинации студентов для индивидуального формирования одной группы (http://www.merriampark.com/comb.htm). Используя этот итератор и некоторую рекурсию, я могу найти ПЕРЕСТАНОВКИ возможных комбинаций групп. Однако порядок, в котором выбираются группы, не имеет значения, и я хотел бы минимизировать время выполнения. Итак, как мне найти уникальные КОМБИНАЦИИ возможных групп?

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

Я хорошо знаю Ruby и хуже Java/Python. Спасибо заранее за любые советы!


person Jordan Smith    schedule 26.10.2009    source источник
comment
Возможно, взгляните сюда, особенно на функции multiset. Это Perl, но он должен дать вам кое-какой код: search.cpan.org/perldoc /Математика::Комбинаторика   -  person Telemachus    schedule 26.10.2009
comment
Спасибо ... знание того, что это мультимножество, также улучшит мой гуглинг.   -  person Jordan Smith    schedule 26.10.2009


Ответы (4)


Ну, там (30C5*25C5*20C5*15C5*10< sub>C5*5C5)/6! = 30!/(6!*5!6) = 123 378 675 083 039 376 различных частей из 30 в группы по 5, поэтому создание их всех займет некоторое время, независимо от того, какой метод вы используете.

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

     find_partition = lambda do |elts|
        if elts.empty?
         [[]]
        else
         highest = elts.pop
         elts.combination(4).map do |others|
            find_partition[elts - others].map { |part| part << [highest,*others] }
         end.inject(:+)
        end
     end
     find_partition[(1..30).to_a]

Таким образом, вы создаете каждый раздел только один раз

person rampion    schedule 26.10.2009
comment
Не могли бы вы уточнить, почему это не просто 30c5? Я не смотрел на комбинаторную математику с 2001 года! - person Josh; 26.10.2009
comment
Ну, есть 30c5 способов выбора первой группы из 5, 25c5 выбора второй,..., 10c5 способов выбора пятой группы 5 и 5c5=1 способов выбора последней. Однако, поскольку мы делаем разбиение, нас не волнует порядок, в котором мы получаем эти группы. Так как их 6! способов заказать 6 групп, делим на 6!. Это дает расширенный продукт в почте. - person rampion; 26.10.2009
comment
Спасибо, Рэмпион... используя эту математику, я смог убедить своего руководителя проекта, что нам нужен другой подход для улучшения масштабируемости. - person Jordan Smith; 29.10.2009

Это старый вопрос, но в любом случае, для протокола, я бы сделал это в Ruby:

class Array
  def groups_of_size(n)
    Enumerator.new do |yielder|
      if self.empty?
        yielder.yield([])
      else
        self.drop(1).combination(n-1).map { |vs| [self.first] + vs }.each do |values|
          (self - values).groups_of_size(n).each do |group|
            yielder.yield([values] + group)
          end   
        end
      end
    end
  end
end

Я использую перечислитель, потому что вывод может расти очень быстро, строгий вывод (например, массив) был бы бесполезен. Пример использования:

>> pp [0, 1, 2, 3, 4, 5].groups_of_size(3).to_a
=> 
[[[0, 1, 2], [3, 4, 5]],
 [[0, 1, 3], [2, 4, 5]],
 [[0, 1, 4], [2, 3, 5]],
 [[0, 1, 5], [2, 3, 4]],
 [[0, 2, 3], [1, 4, 5]],
 [[0, 2, 4], [1, 3, 5]],
 [[0, 2, 5], [1, 3, 4]],
 [[0, 3, 4], [1, 2, 5]],
 [[0, 3, 5], [1, 2, 4]],
 [[0, 4, 5], [1, 2, 3]]]
person tokland    schedule 06.05.2011

Вы можете сделать некоторую пост-обработку перестановок. Некоторый псевдокод (реализуйте на выбранном вами языке...):

// We have a list of lists called 'permutations'
// combinations is an (empty) list of lists
for each permutation in permutations
{
   sortedPermutation = permutation.sort()
   if (! combinations.find(sortedPermutation) )
   {
       combinations.add(sortedPermutation);
   }
}

Вероятно, не самый эффективный; Я бы добавил сортировку и сравнение с кодом, который лично генерирует перестановки.

person Josh    schedule 26.10.2009

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

List<List<Student>> combinations=Combinations(students);
public void GenerateCombinations(int startingIndex, List<List<Student>> currentGroups, int groupsLeft)
{
    if(groupsLeft==0) ProcessCombination(currentGroups);

    for(int i=startingIndex; i<combinations.Count; i++)
    {
        if combinations[i] does not contain a student in current groups
            GenerateCombinations(i+1, currentGroups + combinations[i], groupsLeft -1);
    }
}

Это будет не самый эффективный метод, но он должен генерировать все комбинации групп. Я подозреваю, что лучшую производительность можно было бы получить, если бы вы генерировали временные списки комбинаций, где все группы, которые не могут возникнуть, были удалены, но это было бы немного сложнее.

Небольшое отступление: должно быть 142 506 комбинаций из 30 студентов, чтобы сформировать одну группу из 5 человек. Мои математические способности «сарказм» и «сарказм» подсказывают, что должно быть около 10^17 = 100 квадриллионов комбинаций групп студентов ( 30!/((5!^6)*6!); 30! порядок учеников, порядок 6 групп по 5 не имеет значения, и порядок этих 6 групп не имеет значения). Возможно, вы сидите там некоторое время, ожидая, пока это закончится.

person CoderTao    schedule 26.10.2009