这个问题的实际应用是心理学研究中的小组分配,但理论上的表述是:
我有一个矩阵(实际矩阵是27x72,但我会选择一个4x8作为例子):
1 0 1 0
0 1 0 1
1 1 0 0
0 1 1 0
0 0 1 1
1 0 1 0
1 1 0 0
0 1 0 1
我想从该矩阵中选取一半的行,使得列总数相等(从而有效地创建具有等效列总数的两个矩阵)。我无法重新排列行中的值。
我尝试了一些暴力解决方案,但我的矩阵太大而无法有效,即使先选择了一些随机限制。在我看来,搜索空间可以用更好的算法来约束,但到目前为止我还没有想到一个。有任何想法吗?也有可能没有解决方案,因此算法必须能够处理。我一直在R工作,但我可以轻松切换到python。
更新 通过ljeabmreosn找到了解决方案。 Karmarkar-Karp非常适合算法,并且将行转换为基数73受到启发。我很难找到能够实际给出子序列的代码,而不仅仅是最终的差异(也许大多数人只对摘要中的这个问题感兴趣?)。无论如何这是代码:
首先我按照海报的建议将我的行转换为基数73。为此,我在python中使用了basein包,定义了一个包含73个字符的字母,然后使用basein.decode函数转换为decimel。
对于算法,我只是添加了代码来打印来自Tim Peters的邮件列表消息中的子序列索引:https://mail.python.org/pipermail/tutor/2001-August/008098.html
from __future__ import nested_scopes
import sys
import bisect
class _Num:
def __init__(self, value, index):
self.value = value
self.i = index
def __lt__(self, other):
return self.value < other.value
# This implements the Karmarkar-Karp heuristic for partitioning a set
# in two, i.e. into two disjoint subsets s.t. their sums are
# approximately equal. It produces only one result, in O(N*log N)
# time. A remarkable property is that it loves large sets: in
# general, the more numbers you feed it, the better it does.
class Partition:
def __init__(self, nums):
self.nums = nums
sorted = [_Num(nums[i], i) for i in range(len(nums))]
sorted.sort()
self.sorted = sorted
def run(self):
sorted = self.sorted[:]
N = len(sorted)
connections = [[] for i in range(N)]
while len(sorted) > 1:
bigger = sorted.pop()
smaller = sorted.pop()
# Force these into different sets, by "drawing a
# line" connecting them.
i, j = bigger.i, smaller.i
connections[i].append(j)
connections[j].append(i)
diff = bigger.value - smaller.value
assert diff >= 0
bisect.insort(sorted, _Num(diff, i))
# Now sorted contains only 1 element x, and x.value is
# the difference between the subsets' sums.
# Theorem: The connections matrix represents a spanning tree
# on the set of index nodes, and any tree can be 2-colored.
# 2-color this one (with "colors" 0 and 1).
index2color = [None] * N
def color(i, c):
if index2color[i] is not None:
assert index2color[i] == c
return
index2color[i] = c
for j in connections[i]:
color(j, 1-c)
color(0, 0)
# Partition the indices by their colors.
subsets = [[], []]
for i in range(N):
subsets[index2color[i]].append(i)
return subsets
if not sys.argv:
print "error no arguments provided"
elif sys.argv[1]:
f = open(sys.argv[1], "r")
x = [int(line.strip()) for line in f]
N = 50
import math
p = Partition(x)
s, t = p.run()
sum1 = 0L
sum2 = 0L
for i in s:
sum1 += x[i]
for i in t:
sum2 += x[i]
print "Set 1:"
print s
print "Set 2:"
print t
print "Set 1 sum", repr(sum1)
print "Set 2 sum", repr(sum2)
print "difference", repr(abs(sum1 - sum2))
这给出了以下输出:
Set 1:
[0, 3, 5, 6, 9, 10, 12, 15, 17, 19, 21, 22, 24, 26, 28, 31, 32, 34, 36, 38, 41, 43, 45, 47, 48, 51, 53, 54, 56, 59, 61, 62, 65, 66, 68, 71]
Set 2:
[1, 2, 4, 7, 8, 11, 13, 14, 16, 18, 20, 23, 25, 27, 29, 30, 33, 35, 37, 39, 40, 42, 44, 46, 49, 50, 52, 55, 57, 58, 60, 63, 64, 67, 69, 70]
Set 1 sum 30309344369339288555041174435706422018348623853211009172L
Set 2 sum 30309344369339288555041174435706422018348623853211009172L
difference 0L
在几秒钟内提供适当子集的索引。谢谢大家!
答案 0 :(得分:2)
假设矩阵中的每个条目可以是0或1,这个问题似乎与只有伪多项式时间算法的Partition Problem在同一个族中。设{1}为矩阵中的行数,r
为矩阵中的列数。然后,将每一行编码为基数c
的{{1}}位数。这是为了确保在添加每个编码时不需要携带,因此该基数中的等价数字将等同于两列的行,其列总和是等价的。因此,在您的示例中,您将每行转换为4位数的基数9.这将产生数字(转换为基数10):
1010 9 =&gt; 738 <子> 10 子>
0101 9 =&gt; 82 <子> 10 子>
1100 9 =&gt; 810 <子> 10 子>
0110 9 =&gt; 90 <子> 10 子>
0011 9 =&gt; 10 <子> 10 子>
1010 9 =&gt; 738 <子> 10 子>
1100 9 =&gt; 810 <子> 10 子>
0101 9 =&gt; 82 <子> 10 子>
尽管您可能无法在此方法中使用伪多项式时间算法,但您可以使用简单的启发式算法和一些决策树来尝试加速暴力。使用上面的数字,您可以尝试使用Karmarkar-Karp heuristic。下面实现的是Python 3中算法的第一步:
c
Here是完全实现的算法。请注意,此方法只是一种启发式方法,可能无法找到最佳分区。