Сначала рассмотрим следующий вариант последовательного алгоритма. Возьмите элемент a
и назначьте его подмножеству # 0 (это всегда верно, потому что порядок подмножеств внутри раздела не имеет значения). Следующий элемент b
может принадлежать либо к тому же подмножеству, что и a
, либо к другому, то есть к подмножеству # 1. Тогда элемент c
принадлежит либо # 0 (вместе с a
), либо # 1 (вместе с b
, если он отделен от a
), либо своему собственному подмножеству (которое будет # 1, если # 0 = {a
, b
} или # 2, если # 0 = {a
} и # 1 = {b
}). И так далее. Таким образом, вы добавляете новые элементы один за другим в частично построенные разделы, производя несколько возможных выходных данных для каждого входа - пока вы не поместите все элементы. Ключом к распараллеливанию является то, что каждый неполный раздел может быть дополнен новыми элементами независимо, то есть параллельно со всеми другими вариантами.
Алгоритм может быть реализован разными способами. Я бы использовал рекурсивный подход, при котором функции присваивается частично заполненный массив и его текущая длина копирует массив столько раз, сколько есть возможных значений для следующего элемента (который на единицу больше текущего последнего значения массива ), устанавливает следующий элемент для каждого возможного значения и вызывает себя рекурсивно для каждого нового массива с увеличенной длиной. Этот подход особенно хорош для кражи параллельных двигателей, таких как cilk или tbb . Также возможна реализация, аналогичная предложенной @swen: вы используете коллекцию всех неполных разделов и пул потоков, и каждый поток берет один раздел из коллекции, создает все возможные расширения и возвращает их обратно в коллекцию; разделы со всеми добавленными элементами, очевидно, должны входить в другую коллекцию.