Найти n-й набор powerset

Я пытаюсь найти n-th установить в powerset. От n-th Я имею в виду, что powerset генерируется в следующем порядке — сначала по размеру, а затем по лексикографическому признаку — и так, индексы наборов в powerset [a, b, c] является:

0 - []
1 - [a]
2 - [b]
3 - [c]
4 - [a, b]
5 - [a, c]
6 - [b, c]
7 - [a, b, c]

При поиске решения все, что я мог найти, — это алгоритм возврата n-й перестановки списка элементов — например, Вот.

контекст:

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

Требования:

  • Я могу сохранить только два вектора одновременно: первый с исходными элементами в списке, а второй с n-th установить от powerset V — Вот почему я хочу иметь n-th set функционировать здесь;
  • Мне нужно это сделать не в линейном времени на пространстве решений — это означает, что он не может перечислить все множества, и они выбирают n-th один;
  • Моя первоначальная идея состоит в том, чтобы использовать биты для представления позиций и получить правильное отображение того, что мне нужно — как «неполное» решение, которое я опубликовал.

5

Решение

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

Как говорится в комментарии, я нахожу это определение «лексикографического упорядочения» несколько странным, поскольку я бы сказал, что лексикографическое упорядочение будет следующим: [], [a], [ab], [abc], [ac], [b], [bc], [c], Но раньше мне приходилось делать перечисление «сначала по размеру, потом по лексикографии».

// Generate bitmaps representing all subsets of a set of k elements,
// in order first by (ascending) subset size, and then lexicographically.
// The elements correspond to the bits in increasing magnitude (so the
// first element in lexicographic order corresponds to the 2^0 bit.)
//
// This function generates and returns the next bit-pattern, in circular order
// (so that if the iteration is finished, it returns 0).
//
template<typename UnsignedInteger>
UnsignedInteger next_combination(UnsignedInteger comb, UnsignedInteger mask) {
UnsignedInteger last_one = comb & -comb;
UnsignedInteger last_zero = (comb + last_one) &~ comb & mask;
if (last_zero) return comb + last_one + (last_zero / (last_one * 2)) - 1;
else if (last_one > 1) return mask / (last_one / 2);
else return ~comb & 1;
}

Строка 5 выполняет бит-хакерский эквивалент замены (расширенного) регулярного выражения, которая находит последний 01 в строке переворачивает 10 и сдвигает все следующие 1до упора вправо.

s/01(1*)(0*)$/10\2\1/

Строка 6 делает это (только если предыдущий не удался), чтобы добавить еще один 1 и сдвинуть 1до упора вправо:

s/(1*)0(0*)/\21\1/

Я не знаю, помогает ли это объяснение или мешает 🙂


Вот быстрый и грязный драйвер (аргумент командной строки — это размер набора, по умолчанию 5, максимальное количество битов в беззнаковых длинных):

#include <iostream>

template<typename UnsignedInteger>
std::ostream& show(std::ostream& out, UnsignedInteger comb) {
out << '[';
char a = 'a';
for (UnsignedInteger i = 1; comb; i *= 2, ++a) {
if (i & comb) {
out << a;
comb -= i;
}
}
return out << ']';
}

int main(int argc, char** argv) {
unsigned int n = 5;
if (argc > 1) n = atoi(argv[1]);
unsigned long mask = (1UL << n) - 1;
unsigned long comb = 0;
do {
show(std::cout, comb) << std::endl;
comb = next_combination(comb, mask);
} while (comb);
return 0;
}

Трудно поверить, что эта функция может быть полезна для набора из более чем 64 элементов, учитывая размер перечисления, но может быть полезно перечислить некоторую ограниченную часть, такую ​​как все подмножества трех элементов. В этом случае бит-хакерство действительно полезно, только если модификация помещается в одно слово. К счастью, это легко проверить; вам просто нужно выполнить вычисления, как указано выше, для последнего слова в наборе битов, вплоть до проверки на last_zero быть ноль. (В этом случае вам не нужно maskи, возможно, вы захотите выбрать другой способ задания заданного размера.) Если last_zero оказывается равным нулю (что на самом деле будет довольно редко), тогда вам нужно выполнить преобразование каким-то другим способом, но принцип тот же: найдите первое 0 который предшествует 1 (обратите внимание на случай, когда 0 в конце слова и 1 в начале следующего); изменить 01 в 10выясни сколько 1s вы должны двигаться, и переместить их до конца.

5

Другие решения

Рассматривая список элементов L = [a, b, c], powerset of L дан кем-то:

P(L) = {
[],
[a], [b], [c],
[a, b], [a, c], [b, c],
[a, b, c]
}

Рассматривая каждую позицию как немного, у вас будет отображение:

id  | positions - integer | desired set
0  |  [0 0 0]  -    0    |  []
1  |  [1 0 0]  -    4    |  [a]
2  |  [0 1 0]  -    2    |  [b]
3  |  [0 0 1]  -    1    |  [c]
4  |  [1 1 0]  -    6    |  [a, b]
5  |  [1 0 1]  -    5    |  [a, c]
6  |  [0 1 1]  -    3    |  [b, c]
7  |  [1 1 1]  -    7    |  [a, b, c]

Как видите, id не отображается напрямую на целые числа. Необходимо применить правильное отображение, чтобы у вас было:

id  | positions - integer |  mapped  - integer
0  |  [0 0 0]  -    0    |  [0 0 0] -    0
1  |  [1 0 0]  -    4    |  [0 0 1] -    1
2  |  [0 1 0]  -    2    |  [0 1 0] -    2
3  |  [0 0 1]  -    1    |  [0 1 1] -    3
4  |  [1 1 0]  -    6    |  [1 0 0] -    4
5  |  [1 0 1]  -    5    |  [1 0 1] -    5
6  |  [0 1 1]  -    3    |  [1 1 0] -    6
7  |  [1 1 1]  -    7    |  [1 1 1] -    7

Чтобы попытаться решить эту проблему, я решил использовать бинарное дерево для отображения — я публикую его, чтобы кто-то мог увидеть решение из него:

                                        #
______________|_____________
a               /                             \
_____|_____                   _______|______
b        /           \                 /              \
__|__         __|__           __|__            __|__
c    /     \       /     \         /     \          /     \
[ ]     [c]    [b]   [b, c]    [a]   [a, c]    [a, b]  [a, b, c]
index:      0       3      2       6       1      5         4         7
4

Предположим, ваш набор имеет размер N.

Итак, существует (N выбирают k) наборов размера k. Вы можете найти правильное k (то есть размер n-го набора) очень быстро, просто вычитая (N выбирайте k) из n, пока n не станет отрицательным. Это сводит вашу проблему к поиску n-го k-подмножества N-множества.

Первые (N-1 выбирают k-1) k-подмножества вашего N-набора будут содержать его наименьший элемент. Таким образом, если n меньше (N-1 выбирает k-1), выберите первый элемент и рекурсивно на остальной части набора. В противном случае у вас есть один из (N-1 выберите k) других наборов; отбросьте первый элемент, вычтите (N-1 выберите k-1) из n и рекурсивно.

Код:

#include <stdio.h>

int ch[88][88];
int choose(int n, int k) {
if (n<0||k<0||k>n) return 0;
if (!k||n==k) return 1;
if (ch[n][k]) return ch[n][k];
return ch[n][k] = choose(n-1,k-1) + choose(n-1,k);
}

int nthkset(int N, int n, int k) {
if (!n) return (1<<k)-1;
if (choose(N-1,k-1) > n) return 1 | (nthkset(N-1,n,k-1) << 1);
return nthkset(N-1,n-choose(N-1,k-1),k)<<1;
}

int nthset(int N, int n) {
for (int k = 0; k <= N; k++)
if (choose(N,k) > n) return nthkset(N,n,k);
else n -= choose(N,k);
return -1; // not enough subsets of [N].
}

int main() {
int N,n;
scanf("%i %i", &N, &n);
int a = nthset(N,n);
for (int i=0;i<N;i++) printf("%i", !!(a&1<<i));
printf("\n");
}
2
По вопросам рекламы [email protected]