【问题标题】:Improving algorithm that uses the cartesian product改进使用笛卡尔积的算法
【发布时间】:2018-02-03 13:37:26
【问题描述】:

问题

假设您有n 整数列表,其中每个列表仅包含从1n 范围内的整数。例如,对于n = 4,我们可能有:

a_1 = [1, 2]
a_2 = [3]
a_3 = [4, 1, 1]
a_4 = [2, 3]

现在我的问题是:我可以勾选那些n 列表中1n 之间的所有整数,但是一旦我找到一个数字,我可以'不再使用该列表来查找后续数字?

例如,在上面n = 4的例子中,我可以从a_1中选择1,从a_4中选择2,从a_2中选择3,对于a_3,我可以选择4,因此我已经填写了所有从 1 到 4 的数字,但每个列表只使用一次

我找不到范围(因此应该返回 False)的示例是:

a_1 = [1, 2]
a_2 = [3, 3, 5]
a_3 = [4]
a_4 = [5]
a_5 = [3, 4, 5]

原因是因为如果我从 a_1 中选择 1,我不能再从任何列表中选择 2。

方法

这是我目前的直截了当的方法。我制作列表的笛卡尔积并检查是否有任何排序后的范围。

import itertools

def fillRange(lists):
  cartesian = itertools.product(*lists)
  return any([sorted(comb) == range(1, len(lists) + 1) for comb in cartesian])

问题

虽然我的方法有效,但对于大型列表,它变得有点低效。关于如何改进此算法的任何想法?

谢谢!

【问题讨论】:

  • 好的,那么你想要的输入lists = [a_1, a_2, a_3, a_4]的输出到底是什么?
  • return any([sorted(comb) == range(1, len(lss) + 1) for comb in cartesian]) 中删除[]s 会更好吗?就目前而言,您将始终首先处理完整的笛卡尔积,评估条件以构建布尔列表,然后将该列表传递给any 以查看是否有任何为真。如果去掉[]s,那么any会在找到第一个匹配条件时返回True。
  • @Chris_Rands 我想要的输出是一个布尔值,指示是否可以使用这些列表找到范围。
  • @McGuire 例如True?
  • @Chris_Rands 查看我更新的编辑,有很多示例无法实现从 1 到 n 的范围。

标签: python algorithm optimization cartesian-product


【解决方案1】:

您可以将其表述为二分图中的最大流问题,其中左节点对应于列表,右节点对应于整数 1 到 n。

如果整数在对应的列表中,则图中存在一条边。

图中的所有容量都等于 1。

如果你能找到从左侧到右侧的大小为 n 的流,那么问题就解决了。

下面的 Python 代码:

import networkx as nx

a_1 = [1, 2]
a_2 = [2]
a_3 = [4, 1, 1]
a_4 = [2, 3]
A = [a_1,a_2,a_3,a_4]
n = 4

G=nx.DiGraph()
for i,a in enumerate(A):
    for j in set(a):
        l = 'list'+str(i)
        G.add_edge(l,j,capacity=1)
        G.add_edge('start',l,capacity=1)
for j in range(1,n+1):
    G.add_edge(j,'dest',capacity=1)
v,flow = nx.maximum_flow(G,'start','dest')
if v<n:
    print 'Impossible'
else:
    for i,a in enumerate(A):
        for j in set(a):
            if flow['list'+str(i)][j]>0:
                print 'Use',j,'from list',a

打印出来:

Use 1 from list [1, 2]
Use 2 from list [2]
Use 4 from list [4, 1, 1]
Use 3 from list [2, 3]

【讨论】:

  • 嗨彼得,这是一个很好的解决方案,有什么复杂性?
  • 应该是 O(Esqrt(n)) 其中 E 是所有列表的总长度
【解决方案2】:

您可以先测试最受限制的列表,然后在将元素添加到解决方案集中时更新其他列表中的备选方案,而不是按顺序测试所有组合,从而大大加快这一速度。这样,您无需回溯一次即可“解决”两个示例。

def search(n, lists):
    if n == 0:
        yield []
    else:
        lists = [l for l in lists if l != []]
        if len(lists) >= n:
            least = min(lists, key=len)
            for val in least:
                new = [[x for x in lst if x != val] for lst in lists if lst is not least]
                for res in search(n-1, new):
                    yield [val] + res

以下是您的两个示例的一些调试/跟踪输出,以帮助理解。第一个值是n,然后是lists,最后是之前选择的val

4 [[1, 2], [3], [4, 1, 1], [2, 3]] None
3 [[1, 2], [4, 1, 1], [2]] 3
2 [[1], [4, 1, 1]] 2
1 [[4]] 1
0 [] 4 --> solution
[3, 2, 1, 4]

5 [[1, 2], [3, 3, 5], [4], [5], [3, 4, 5]] None
4 [[1, 2], [3, 3, 5], [5], [3, 5]] 4
3 [[1, 2], [3, 3], [3]] 5
2 [[1, 2], []] 3 --> abort

如果您还想要获取元素的列表的索引,代码会稍微复杂一些,但并不复杂:

def search(n, lists):
    if n == 0:
        yield []
    else:
        if sum(1 for l in lists if l) >= n:
            i = min(range(len(lists)), key=lambda x: (lists[x] == [], len(lists[x])))
            for val in lists[i]:
                new = [[x for x in lst if x != val] if lst is not lists[i] else [] for lst in lists]
                for res in search(n-1, new):
                    yield [(i, val)] + res

你的第一个例子的结果是[(1, 3), (3, 2), (0, 1), (2, 4)]

【讨论】:

  • 我喜欢!这或多或少是我想要的,谢谢。它看起来与@PaulMcG 在他的第二个解决方案中提出的非常相似;我现在进退两难,要检查哪个答案是正确的,如果有的话。
  • @McGuire 是的,方法类似:我选择元素最少的列表,他选择列表中最少的元素。由于列表的数量与唯一元素的数量相同,因此它们或多或少是等效的,但在某些情况下,其中一个或另一个更快。不过,我的回答提供了完整的算法......
  • @wwii 是的。 (如果您的意思是该列表有一个6,但应该有一个5:好吧,我不检查数字是否在某个范围内,而只检查是否有n 不同的数字找到了。)
  • @wwii 我还是不明白你的意思。 OP 说给定的列表包含“从 1 到 n”的数字,所以我将其视为给定的。如果输入有效,则输出也将是从 1 到 n 的数字。
  • @wwii 没关系。 :-) 评论不能被否决,但如果你愿意,你可以删除自己的 cmets。
【解决方案3】:

笛卡尔积对我来说似乎是最直接的。我将执行以下操作来简化您的代码:

  • 如我在 cmets 中提到的,从您的 any 表达式中删除 []

  • 在计算笛卡尔积之前将所有输入列表折叠到集合 - 处理同一列表中的重复值没有意义

  • range(1, len(lists)+1) 保存到一个局部变量并与之进行比较,而不是每次都重新创建范围(这是一种称为“不变提升”的常见优化技术,其中计算的表达式在循环被“提升”出循环并且只计算一次)

但最终,计算输入列表的 a 笛卡尔的基本算法,然后查找任何值为 1-n 的值仍然是您最初编写的。

def fillRange(lists):
  cartesian = itertools.product(*(set(x) for x in lists))
  target = list(range(1, len(lists) + 1))
  return any(sorted(comb) == target for comb in cartesian)

【讨论】:

  • 感谢 Paul 的方法,但我正在寻找一种完全减少使用笛卡尔积的新方法。我确信一定有更快的方法来解决这个问题,但我只是没有看到它。
【解决方案4】:

这可以看作是matching in a bipartite graph的问题。事实证明,Hall's marriage theorem 告诉你答案(即匹配是否存在,而不是匹配本身)。这是一个可能的实现(为方便起见,使用 NumPy):

from itertools import chain, combinations
import numpy as np

# Recipe from itertools docs: https://docs.python.org/3/library/itertools.html#itertools-recipes
def powerset(iterable):
    s = list(iterable)
    return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))


def has_matching(lists):
    n = len(lists)
    m = np.array([np.logical_or.reduce(np.arange(1, n + 1)[:, np.newaxis] == lst, axis=1)
                  for lst in lists])
    m = m.astype(int)
    for s in powerset(range(n)):
        if np.sum(np.logical_or.reduce(m[s, :], axis=0)) < len(s):
            return False
    return True


lists1 = [[1, 2],
          [3],
          [4, 1, 1],
          [2, 3]]
print(has_matching(lists1))
>>> True

lists2 = [[1, 2],
          [3, 3, 5],
          [4],
          [5],
          [3, 4, 5]]
print(has_matching(lists2))
>>> False

但是,这需要你遍历{1, ..., n} 的每个子集,所以我猜算法是 O(2N)。不是很好,但可能比遍历整个笛卡尔积要好,我猜应该是 O(NN)。

【讨论】:

  • 霍尔的婚姻问题似乎很有趣。我仍然不完全确定 O(2^N) 是我们在这个问题上能做的最好的,你不觉得吗?
  • @McGuire 好吧,这已经比尝试所有可能的组合要好。我缺乏确保没有更好算法的数学权威,但霍尔定理确实说“当且仅当”。但是,也许有更好的实现来检查条件,使用动态编程或其他方法......
  • 使用Hopcroft-Karp algorithm 来解决匹配问题可能会更好——这将是 O(Esqrt(V)) 而不是 O(2^V)
  • @PeterdeRivaz 这听起来像是对自己问题的回答。
  • @PeterdeRivaz 但问题实际上不是确切的封面问题(有 DLX)吗?
【解决方案5】:

您可以尝试映射哪些值出现在哪个列表中,然后从那里分解您的问题。这段代码构建了这种反向查找:

In[38]: from collections import defaultdict
In[39]: occurrences = defaultdict(set)
In[40]: for i,ll in enumerate(lists):
   ...:     for x in ll:
   ...:         occurrences[x].add(i)
   ...:         
In[41]: print(occurrences)
defaultdict(<class 'set'>, {1: {0, 2}, 2: {0, 3}, 3: {1, 3}, 4: {2}})
In[42]: print(dict(occurrences.items()))
{1: {0, 2}, 2: {0, 3}, 3: {1, 3}, 4: {2}}

例如,您一眼就可以看到, 4 仅存在于list[2] 中(即您原始问题中的a_3)。从那里,如果您从其他值中消除 2,则 1 仅存在于 list[0] 中。去掉0表示2只存在于list[3]中,则3只能从list[1]中得到。如果在执行此连续消除过程中,任何可供选择的集合变为空,则没有解决方案。

【讨论】:

  • 感谢 Paul,这或多或少是我要找的,请参阅我对 tobias 的评论。
  • 如何搜索occurrences?作为图表??
  • occurrences 只是一个美化的集合字典。要查找的列表值是键,它们所在的列表是值。
猜你喜欢
  • 2015-10-21
  • 1970-01-01
  • 2011-02-03
  • 2011-03-24
  • 2017-03-07
  • 1970-01-01
  • 2013-10-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多