从矩阵中有效地选择行以使列总数相等的算法

时间:2017-07-26 16:56:37

标签: algorithm matrix

这个问题的实际应用是心理学研究中的小组分配,但理论上的表述是:

我有一个矩阵(实际矩阵是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

在几秒钟内提供适当子集的索引。谢谢大家!

1 个答案:

答案 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是完全实现的算法。请注意,此方法只是一种启发式方法,可能无法找到最佳分区。