Подсчет комбинаций пар предметов из нескольких списков без повторов - PullRequest
3 голосов
/ 11 июня 2009

Учитывая сценарий, в котором у нас есть несколько списков пар предметов, например:

  1. {12,13,14,23,24}
  2. {14,15,25}
  3. {16,17,25,26,36}

, где 12 - пара элементов '1' и '2' (и, следовательно, 21 - эквивалент 12), мы хотим подсчитать количество способов, которыми мы можем выбрать пары элементов из каждого из списков, так что нет один элемент повторяется. Вы должны выбрать одну и только одну пару из каждого списка. Количество элементов в каждом списке и количество списков является произвольным, но можно предположить, что существует по крайней мере два списка, по крайней мере, с одной парой элементов в списке. А пары состоят из символов из конечного алфавита, предположим цифры [1-9]. Кроме того, список не может содержать одинаковые пары {12,12} или {12,21}, а также не может содержать симметричные пары {11}.

Более конкретно, в приведенном выше примере, если мы выберем пару элементов 14 из первого списка, то единственный выбор, который мы имеем для второго списка, - это 25, потому что 14 и 15 содержат «1». И, следовательно, единственный выбор из третьего списка - 36, потому что 16 и 17 содержат «1», а 25 и 26 содержат «2».

Кто-нибудь знает о эффективном способе подсчета суммарных комбинаций пар элементов без прохождения каждой перестановки вариантов выбора и вопроса «действительно ли это выбор?», Поскольку каждый из списков может содержать сотни пар предметов?


UPDATE

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

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

  1. {12,23,34,45,67}
  2. {12,23,34,45,67}
  3. {12,23,34,45,67}

Ответы [ 7 ]

3 голосов
/ 12 июня 2009

Проблема # P-полная. Это даже сложнее, чем NP-полная. Это так же сложно, как найти количество удовлетворяющих назначений для экземпляра SAT.

Сокращение от Идеальное совпадение . Предположим, у вас есть граф G = {V, E}, где E, множество ребер, представляет собой список пар вершин (тех пар, которые соединены ребром). Затем закодируйте экземпляр «пары элементов», получив |V|/2 копий E. Другими словами, количество копий E равно половине числа вершин. Теперь «попадание» в вашем случае будет соответствовать |V|/2 ребрам без повторяющихся вершин, подразумевая, что все |V| вершины были покрыты. Это определение идеального соответствия. И каждое идеальное совпадение будет хитом - это переписка 1-1.

2 голосов
/ 12 июня 2009

Позволяет сказать, что каждый элемент в списках является узлом в графе. Между двумя узлами есть грань, если их можно выбрать вместе (у них нет общего символа). Нет границы между двумя узлами одного и того же списка. Если у нас есть n списков, проблема состоит в том, чтобы найти количество кликов размера n в этом графе. Нет клики, которая больше чем n элементов. Учитывая, что выяснение, существует ли хотя бы одна такая клика, является np-полной, я думаю, что эта проблема является np-полной. Смотри: http://en.wikipedia.org/wiki/Clique_problem

Как уже указывалось, мы должны доказать, что решение этой проблемы может решить проблему Клика, чтобы показать, что она является NP-полной. Если мы можем посчитать количество требуемых наборов, то есть количество клик размера n, мы узнаем, существует ли хотя бы одна клика размером n. К сожалению, если нет клики размера n, то мы не знаем, есть ли клики с размером k

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

Я все еще чувствую, что это NP-Complete

1 голос
/ 16 декабря 2009

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

/*
 *  Main authors:
 *     Mikael Zayenz Lagerkvist <lagerkvist@gecode.org>
 *
 *  Copyright:
 *     Mikael Zayenz Lagerkvist, 2009
 *
 *  Permission is hereby granted, free of charge, to any person obtaining
 *  a copy of this software and associated documentation files (the
 *  "Software"), to deal in the Software without restriction, including
 *  without limitation the rights to use, copy, modify, merge, publish,
 *  distribute, sublicense, and/or sell copies of the Software, and to
 *  permit persons to whom the Software is furnished to do so, subject to
 *  the following conditions:
 *
 *  The above copyright notice and this permission notice shall be
 *  included in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
#include <gecode/driver.hh>
#include <gecode/int.hh>
#include <gecode/minimodel.hh>

using namespace Gecode;

namespace {
  /// List of specifications
  extern const int* specs[];
  /// Number of specifications
  extern const unsigned int n_specs;
}


/**
 * \brief Selecting pairs
 *
 * Given a set of lists of pairs of values, select a pair from each
 * list so that no value is selected more than once.
 *
 */
class SelectPairs : public Script {
protected:
  /// Specification
  const int* spec;
  /// The values from all selected pairs
  IntVarArray s;
public:
  /// The actual problem
  SelectPairs(const SizeOptions& opt)
    : spec(specs[opt.size()]),
      s(*this,spec[0] * 2,Int::Limits::min, Int::Limits::max) {

    int pos = 1; // Position read from spec
    // For all lists
    for (int i = 0; i < spec[0]; ++i) {
      int npairs = spec[pos++];
      // Construct TupleSet for pairs from list i
      TupleSet ts;
      for (int p = 0; p < npairs; ++p) {
        IntArgs tuple(2);
        tuple[0] = spec[pos++];
        tuple[1] = spec[pos++];
        ts.add(tuple);
      }
      ts.finalize();

      // <s[2i],s[2i+1]> must be from list i
      IntVarArgs pair(2);
      pair[0] = s[2*i]; pair[1] = s[2*i + 1];
      extensional(*this, pair, ts);
    }

    // All values must be pairwise distinct
    distinct(*this, s, opt.icl());

    // Select values for the variables
    branch(*this, s, INT_VAR_SIZE_MIN, INT_VAL_MIN);
  }

  /// Constructor for cloning \a s
  SelectPairs(bool share, SelectPairs& sp) 
    : Script(share,sp), spec(sp.spec) {
    s.update(*this, share, sp.s);
  }

  /// Perform copying during cloning
  virtual Space*
  copy(bool share) {
    return new SelectPairs(share,*this);
  }

  /// Print solution
  virtual void
  print(std::ostream& os) const {
    os << "\t";
    for (int i = 0; i < spec[0]; ++i) {
      os << "(" << s[2*i] << "," << s[2*i+1] << ") ";
      if ((i+1) % 10 == 0)
        os << std::endl << "\t";
    }
    if (spec[0] % 10)
      os << std::endl;
  }
};

/** \brief Main-function
 *  \relates SelectPairs
 */
int
main(int argc, char* argv[]) {
  SizeOptions opt("SelectPairs");
  opt.iterations(500);
  opt.size(0);
  opt.parse(argc,argv);
  if (opt.size() >= n_specs) {
    std::cerr << "Error: size must be between 0 and "
              << n_specs-1 << std::endl;
    return 1;
  }
  Script::run<SelectPairs,DFS,SizeOptions>(opt);
  return 0;
}

namespace {
  const int s0[] = {
    // Number of lists
    3,
    // Lists (number of pairs, pair0, pair1, ...)
    5,  1,2,  1,3,  1,4,  2,3,  2,4,
    3,  1,4,  1,5,  2,5,
    5,  1,6,  1,7,  2,5,  2,6,  3,6
  };

  const int s1[] = {
    // Number of lists
    3,
    // Lists (number of pairs, pair0, pair1, ...)
    5,  1,2,  2,3,  3,4,  4,5,  6,7,
    5,  1,2,  2,3,  3,4,  4,5,  6,7,
    5,  1,2,  2,3,  3,4,  4,5,  6,7
  };

  const int *specs[] = {s0, s1};
  const unsigned n_specs = sizeof(specs)/sizeof(int*);
}
1 голос
/ 13 июня 2009

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

Вы можете ускорить вычисление на 1 - 2 величины, используя запоминание и хеширование.

Запоминание запоминает предыдущие результаты аналогичных путей грубой силы. Если вы находитесь в списке n и уже использовали символы x, y, z и ранее сталкивались с этой ситуацией, то вы будете добавлять такое же количество возможных комбинаций из оставшихся списков. Не имеет значения как вы должны перечислить n, используя x, y, z. Поэтому используйте кэшированный результат, если он есть, или продолжите вычисление до следующего списка и проверьте там. Если вы используете рекурсивный алгоритм грубой силы для вычисления результата, но кешируете результаты, это прекрасно работает.

Ключ к сохраненному результату: текущий список и использованные символы. Сортировать символы, чтобы сделать ваш ключ. Я думаю, что здесь есть смысл в словаре или массиве словарей.

Используйте хеширование, чтобы уменьшить количество пар, которые нужно искать в каждом списке. Для каждого списка создайте хеш пар, которые будут доступны, учитывая, что определенное количество символов уже используется. Выберите количество использованных символов, которые вы хотите использовать в своем хэше, в зависимости от того, сколько памяти вы хотите использовать и сколько времени вы хотите потратить на предварительный расчет. Я думаю, что использование 1-2 символов было бы хорошо. Отсортируйте эти хеши по количеству элементов в них ... по возрастанию, а затем оставьте верхний n. Я говорю: выбрасывайте остальные, потому что, если хеш только уменьшает вашу работу, его, вероятно, не стоит хранить (потребуется больше времени, чтобы найти хеш, если их больше). Поэтому, просматривая списки, вы можете быстро просмотреть хэш списка, чтобы увидеть, не использовали ли вы символ в хеше. Если у вас есть, то используйте первый хэш, который подходит для сканирования списка. Первый хеш будет содержать наименьшее количество пар для сканирования. Если вы действительно удобны, вы можете создавать эти хеши по ходу и не тратить время на это заранее.

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

1 голос
/ 12 июня 2009

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

UPDATE

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

Сопоставить набор символов n с набором первых простых чисел n - я назову эту функцию M. В случае символов от 0 до 9 мы получаем следующее отображение и, например, M(4) = 11.

{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} => {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}

Теперь мы можем сопоставить пару (n, m), используя отображение X, на произведение отображений n и m. Это превратит пару (2, 5) в X((2, 5)) = M(2) * M(5) = 5 * 13 = 65.

X((n, m)) = M(n) * M(m)

Зачем все это? Если у нас есть две пары (a, b) и (c, d) из двух списков, сопоставьте их с помощью сопоставления X с x и y и умножьте их, мы получим произведение x * y = M(a) * M(b) * M(c) * M(d) - произведение четырех простых чисел. Мы можем расширить продукт по большему количеству факторов, выбрав пару из каждого списка и получить продукт из 2w простых чисел, если у нас есть w списки. Последний вопрос: что этот продукт говорит нам о парах, которые мы выбрали и умножили? Если выбранные пары образуют правильный выбор, мы никогда не выбираем один символ дважды, следовательно, продукт не содержит простое число дважды и равен квадратов . Если выбор недействителен, продукт содержит хотя бы одно простое число дважды и не является квадратом. И вот последний пример.

X((2, 5)) = 5 * 13 = 65
X((3, 6)) = 7 * 17 = 119
X((3, 4)) = 7 * 11 = 77

Выбор 25 и 36 дает 65 * 119 = 7735 = 5 * 7 * 13 * 17 и является свободным от квадратов, следовательно, действителен. Выбор 36 и 34 дает 119 * 77 = 9163 = 7² * 11 * 17 и не является квадратом, следовательно, недействителен.

Также обратите внимание, как хорошо это сохраняет симметрию - X ((m, n)) = X ((n, m)) - и запрещает симметричные пары, потому что X((m, m)) = M(m) * M(m) не является квадратом свободным.

Я не знаю, поможет ли это, но теперь вы знаете это и можете думать об этом ... ^^


Это первая часть сокращения проблемы 3-SAT к этой проблеме. Проблема 3-SET заключается в следующем.

(!A | B | C) & (B | !C | !D) & (A | !B)

А вот, насколько я понял, это сокращение.

  • м-н представляет пару
  • строка представляет список
  • звездочка представляет уникальный абитальный символ

A1-A1'    !A1-!A1'     => Select A true or false
B1-B1'    !B1-!B1'     => Select B true or false
C1-C1'    !C1-!C1'     => Select C true or false
D1-D1'    !D1-!D1'     => Select D true or false

A1-*   !B1-*   !C1-*   => !A | B | C

A2-!A1'   !A2-A1'      => Create a copy of A
B2-!B1'   !B2-B1'      => Create a copy of B
C2-!C1'   !C2-C1'      => Create a copy of C
D2-!D1'   !D2-D1'      => Create a copy of D

!B2-*  C2-*    D2-*    => B | !C | !D

(How to perform a second copy of the four variables???)

!A3-*  B3-*

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

0 голосов
/ 12 июня 2009

Это хорошая проблема для применения подхода программирования с ограничениями . К списку пакетов, предоставленных Википедией, я добавлю, что в прошлом у меня был хороший опыт использования Gecode ; примеры Gecode также предоставляют базовое руководство по программированию ограничений. Обработка ограничений - хорошая книга на эту тему, если вы хотите глубже понять, как работают алгоритмы.

0 голосов
/ 12 июня 2009

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

Начните со списка 1. Все записи в этом списке являются действительными решениями длины 2 (# = 5). Затем, когда вы вводите список 2. ведите учет всех действительных решений длины 4, которые в итоге получают {1425, 2314, 2315, 2415} (# = 4).

Когда вы добавите третий список в микс, повторите процесс. Вы получите {142536, 241536} (# = 2).

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

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