一个组包含一组实体,每个实体都有一个值。
每个实体可以是多个组的一部分。
问题:查找最大的N个组,其中每个实体在结果中出现的次数不超过一次。如有必要,可以从组中排除实体。
Example:
Entities with values:
A = 2
B = 2
C = 2
D = 3
E = 3
Groups
1: (A,B,C) total value: 2+2+2 = 6
2: (B,D) total value: 2 + 3 = 5
3: (C,E) total value: 2 + 3 = 5
4: (D) total value: 3
5: (E) total value: 3
**Answers**:
Largest 1 group is obviously (A,B,C) with total value 6
Largest 2 groups are (B,D), (C,E) with total value 10
Largest 3 groups are either {(A,B,C),(D),(E)}, {(A,B),(C,E),(D)} or {(A,C), (B,D), (E)} with total value 12
算法的输入数据应为:
如果有多个答案,那么找到其中一个就足够了。
我提供了一个示例来尝试使问题清楚,实际中实体的数量应小于约50,并且组的数量应小于实体的数量。要找到的N组的数量将在1到10之间。
我目前正在通过生成N组的所有可能组合来解决此问题,排除包含重复实体的结果,然后选择具有最大总值的组合。这当然是非常低效的,但我无法理解如何以更有效的方式获得一般结果。
我的问题是,是否有可能以更有效的方式解决这个问题,如果是这样,怎么做?任何提示或答案都非常感谢。
修改
要明确的是,在我的解决方案中,我会生成“假”组,其中重复的实体被排除在“真实”组之外。在示例中,实体(B,C,D,E)是重复的(存在于多个组中。然后对于组1(A,B,C),我添加假组(A,B),(A,C) ,(A)我为其生成组合的组列表。
答案 0 :(得分:2)
这个问题可以表示为线性整数程序。虽然整数编程在复杂性方面不是非常有效,但是对于这么多变量它可以很快地工作。
以下是我们将此问题转换为整数程序的方法。
设v
为表示实体值的大小为K
的向量。
让G
成为定义组的K x M
二进制矩阵:G(i,j)=1
表示实体i
属于组j
和G(i,j)=0
否则
让x
为大小为M
的二进制向量,表示组的选择:x[j]=1
表示我们选择组j
。
设y
为大小为K
的二进制向量,表示包含实体:y[i]=1
表示实体i
包含在结果中。
我们的目标是选择x
和y
,以便在以下条件下最大化sum(v*y)
:
G x >= y
...所有包含的实体必须属于至少一个选定的组sum(x) = N
...我们正好选择N组。下面是R中的一个实现。它使用lpSolve
库,lpsolve的接口。
library(lpSolve)
solver <- function(values, groups, N)
{
n_group <- ncol(groups)
n_entity <- length(values)
object <- c(rep(0, n_group), values)
lhs1 <- cbind(groups, -diag(n_entity))
rhs1 <- rep(0, n_entity)
dir1 <- rep(">=", n_entity)
lhs2 <- matrix(c(rep(1, n_group), rep(0, n_entity)), nrow=1)
rhs2 <- N
dir2 <- "="
lhs <- rbind(lhs1, lhs2)
rhs <- c(rhs1, rhs2)
direc <- c(dir1, dir2)
lp("max", object, lhs, direc, rhs, all.bin=TRUE)
}
values <- c(A=2, B=2, C=2, D=3, E=3)
groups <- matrix(c(1,1,1,0,0,
0,1,0,1,0,
0,0,1,0,1,
0,0,0,1,0,
0,0,0,0,1),
nrow=5, ncol=5)
rownames(groups) <- c("A", "B", "C", "D", "E")
ans <- solver(values, groups, 1)
print(ans)
names(values)[tail(ans$solution, length(values))==1]
# Success: the objective function is 6
# [1] "A" "B" "C"
ans <- solver(values, groups, 2)
print(ans)
names(values)[tail(ans$solution, length(values))==1]
# Success: the objective function is 10
# [1] "B" "C" "D" "E"
ans <- solver(values, groups, 3)
print(ans)
names(values)[tail(ans$solution, length(values))==1]
# Success: the objective function is 12
# [1] "A" "B" "C" "D" "E"
下面是看看这如何解决大问题。它在一秒钟内结束。
# how does it scale?
n_entity <- 50
n_group <- 50
N <- 10
entity_names <- paste("X", 1:n_entity, sep="")
values <- sample(1:10, n_entity, replace=TRUE)
names(values) <- entity_names
groups <- matrix(sample(c(0,1), n_entity*n_group,
replace=TRUE, prob=c(0.99, 0.01)),
nrow=n_entity, ncol=n_group)
rownames(groups) <- entity_names
ans <- solver(values, groups, N)
print(ans)
names(values)[tail(ans$solution, length(values))==1]
答案 1 :(得分:0)
如果实体值总是正数,我认为您可以在不生成所有组合的情况下获得解决方案:
按最大元素,第二大元素,第n大元素对组进行排序。在这种情况下,你将有3个副本,因为最大的组有3个元素。
对于每个副本,只有在没有包含您已添加的元素的情况下,才能将该组从最大到最小添加到解决方案中。这产生3个结果,取最大值。除非权重可能为负,否则不应该有更大的解决方案。
这是C#
中的一个实现var entities = new Dictionary<char, int>() { { 'A', 2 }, { 'B', 2 }, { 'C', 2 }, { 'D', 3 }, { 'E', 3 } };
var groups = new List<string>() { "ABC", "BD", "CE", "D", "E" };
var solutions = new List<Tuple<List<string>, int>>();
for(int i = 0; i < groups.Max(x => x.Length); i++)
{
var solution = new List<string>();
foreach (var group in groups.OrderByDescending(x => x.Length > i ? entities[x[i]] : -1))
if (!group.ToCharArray().Any(c => solution.Any(g => g.Contains(c))))
solution.Add(group);
solutions.Add(new Tuple<List<string>, int>(solution, solution.Sum(g => g.ToCharArray().Sum(c => entities[c]))));
}
solutions.Dump();
solutions.OrderByDescending(x => x.Item2).First().Dump();
输出: