Вариация на задаче покрытия множества в R / C++
Учитывая универсум элементов U = {1, 2, 3,...,n} и количество множеств в этой вселенной {S1, S2,...,Sm}, какое наименьшее множество, которое мы можем создать, будет охватить хотя бы один элемент в каждом из m наборов?
Например, учитывая следующие элементы U = {1,2,3,4} и наборы S = {{4,3,1},{3,1},{4}}, следующие наборы будут охватывать хотя бы один элемент из каждого набора: {1,4} или {3,4}, поэтому минимальный размер, требуемый здесь, равен 2.
Любые мысли о том, как это можно увеличить, чтобы решить проблему для наборов m=100 или m=1000? Или мысли о том, как кодировать это в R или C++?
Пример данных сверху с использованием R library(sets)
,
s1 <- set(4, 3, 1)
s2 <- set(3, 1)
s3 <- set(4)
s <- set(s1, s2, s3)
ура
4 ответа
Это проблема набора множеств, которая в основном покрыта ролями элементов и взаимозаменяемыми наборами. Если A = {4, 3, 1} и B = {3, 1} и C = {4}, соотношение для сохранения набора элементов
A B C
1 + + -
2 - - -
3 + + -
4 + - +
поэтому вы в основном хотите решить проблему покрытия {A, B, C} множествами 1 = {A, B} и 2 = {} и 3 = {A, B} и 4 = {A, C}.
Вероятно, самый простой способ решения нетривиальных примеров покрытия множеств на практике - найти целочисленный программный пакет с интерфейсом на R или C++. Ваш пример будет представлен как следующая целочисленная программа в формате LP.
Minimize
obj: x1 + x2 + x3 + x4
Subject To
A: x1 + x3 + x4 >= 1
B: x1 + x3 >= 1
C: x4 >= 1
Binary
x1 x2 x3 x4
End
Сначала я неправильно понял сложность проблемы и придумал функцию, которая находит набор, который покрывает m наборов, но потом я понял, что он не обязательно самый маленький:
cover <- function(sets, elements = NULL) {
if (is.null(elements)) {
# Build the union of all sets
su <- integer()
for(si in sets) su <- union(su, si)
} else {
su <- elements
}
s <- su
for(i in seq_along(s)) {
# create set candidate with one element removed
sc <- s[-i]
ok <- TRUE
for(si in sets) {
if (!any(match(si, sc, nomatch=0L))) {
ok <- FALSE
break
}
}
if (ok) {
s <- sc
}
}
# The resulting set
s
}
sets <- list(s1=c(1,3,4), s2=c(1,3), s3=c(4))
> cover(sets) # [1] 3 4
Тогда мы можем рассчитать это:
n <- 100 # number of elements
m <- 1000 # number of sets
sets <- lapply(seq_len(m), function(i) sample.int(n, runif(1, 1, n)))
system.time( s <- cover(sets) ) # 0.53 seconds
Не так уж плохо, но все же не самый маленький.
Очевидное решение: генерировать все перестановки элементов и переходить к функции покрытия и сохранять наименьший результат. Это займет около "навсегда".
Другой подход заключается в создании ограниченного числа случайных перестановок - таким образом вы получите аппроксимацию наименьшего множества.
ns <- 10 # number of samples
elements <- seq_len(n)
smin <- sets
for(i in seq_len(ns)) {
s <- cover(sets, sample(elements))
if (length(s) < length(smin)) {
smin <- s
}
}
length(smin) # approximate smallest length
Если вас просто интересует алгоритм (а не эффективный / выполнимый алгоритм), вы можете просто сгенерировать подмножества юниверса с возрастающей мощностью и проверить, что пересечение со всеми множествами в S не пусто. Остановитесь, как только вы получите тот, который работает; кардинальность минимально возможная.
Сложность этого составляет 2^|U| в худшем случае, я думаю. Учитывая ответ Фу Баха, я не думаю, что вы получите ответ за полиномиальное время...
Если вы ограничите каждый набор двумя элементами, у вас будет покрытие проблемного узла np-complete. Я предполагаю, что более общая проблема также была бы завершена NP (для точной версии).