【问题标题】:Is there a better way to iterate through sets (A, B, C) choose (a, b, c)有没有更好的方法来遍历集合 (A, B, C) 选择 (a, b, c)
【发布时间】:2020-10-30 15:17:14
【问题描述】:

我有 1 到 9 个可能重叠的集合,我需要从每个集合中选择 1 到 3 个元素,这样最后我就没有重复了。

对于每一个有效的排列,我都需要做一些处理。

理想情况下,我想要可扩展的解决方案(这样我就可以学习最佳实践!)

一些坏主意:

  1. 可变数量的嵌套/递归循环。优点:不会在无效选项上浪费时间。缺点:在重复的集合上浪费时间,而且丑陋。

  2. 通过从所有集合的并集中选择所需的总数进行迭代,然后进行验证。优点:更少的嵌套,没有重复的集合。缺点:检查每个无效选项。未知:根据我在 math.stackexchange 上提出的问题,验证可能是即时的,也可能需要处理。

例子:

A={1,3,5,7,9,11,13,15} choose 3  
B={4,7,8,9,10,12} choose 2  
C={1,2,5,6,7,14} choose 2

可能的结果 {1,2,3,4,9,13,15}

有没有比我上面列出的更好的方法来访问每个有效结果?

【问题讨论】:

  • @dmitryro 为什么我们需要代码来讨论算法? OP 发布了他的算法考虑。我认为标签只是一个糟糕的选择。
  • 感谢您修复标签。我在 python 中工作,所以欢迎任何特定于 python 的解决方案。
  • @Paul - 继续讨论这里真正的 ask 是什么的问题——当我阅读这个问题时,很明显正在寻找算法输入。但是python 的标签和 OP 的评论(“欢迎使用 python 特定解决方案”)让我感到困惑。这是否意味着可以提出一个理论上不好但在实践中很好的算法——因为 python 在实现该算法方面做得很好?底线——这里的问题是什么——算法输入或python编程输入或两者兼而有之?

标签: python algorithm set


【解决方案1】:

可以转换从每组中选择不同数量的项目的问题 通过重复 设置所需的次数,即 A 选择 3 ,B 选择 2 ,C 选择 2 变为 从每个 A、A、A、B、B、C、C 中选择 1。 这就是寻找不同代表系统 (sdr) 的问题。

可以通过以下方法找到一个集合的集合是否至少有一个sdr。 设集合为 S = { A_0 , A_1 , A_3 , ... , A_n } 并且 X 是所有 A_i 的并集,并将其元素称为 x_0 , x_1 , x_2 ,... 考虑(二分)图,其中每个 A_i 的节点称为 N(A_i),每个 x_i 的节点称为 N(x_i),如果 x_j 在 A_i 中,则边连接 N(A_i) 和 N(x_j) 寻找 sdr 的问题与寻找覆盖每个 N(A_i) 的该图的匹配的问题相同。这样的匹配是最大匹配,所以如果至少存在一个 Hopcroft Karp algorithm 会找到它。因此,要查看一组集合是否在其图上运行 hopcroft karp 的 sdr,然后检查每个 N(A_i) 的匹配覆盖

我们如何检查输出集是否有效? (例如示例中的 {1,2,3,4,9,13,15}) 有效输出集的元素与输入集 A_i 至少一一对应 即让输出集为 W = { y_0 , y_1, ... , y_n } 那么至少有一种方法可以为每个 y_j 选择一个不同的 A_i 使得 y_j 在 A_i 如果我们构造一个新的集合集合 SN = { B_0 , B_1, B_3, ... , B_N } 其中 B_i 是包含 y_i 的集合 A_j 的集合 找到这个对应关系只是为 SN 找到一个 sdr。所以要检查输出集是否有效,检查是否有这个新集合集合的 sdr。 同样,如果我们对 W 的一个子集进行此构造,那么如果有一个用于 W 的 sdr,就会有一个 sdr

使用模块 networkx 来实现 hopcroft karp 以下检查集合的集合是否具有 sdr

import networkx as nx

def hasSDR(s):
    x=set.union(*s)
    setnames = ["set"+str(i+1) for i in range(len(s)) ]
    B = nx.Graph()
    B.add_nodes_from( setnames , bipartite=0)
    B.add_nodes_from( x , bipartite=1)
    B.add_edges_from( [( setnames[i] , j ) for i in range(len(s)) for j in s[i] ] )
    hk = nx.bipartite.hopcroft_karp_matching(B,top_nodes=setnames)
    return all( i in hk for i in setnames )

然后我们可以通过

找到一个输出集是否有效(假设它的大小正确)
def possibleSDRSet( s , w ):
    sn = [ { j for j in range(len(s)) if i in s[j] } for i in w ]
    return hasSDR( sn )

如果给这个函数 w 的大小小于集合中集合的数量,它只会在 w 是没有有效输出集合的子集时返回 false

遍历 X 的所有长度为 n 的子集,然后测试它们是否是我们可以做的有效输出集(使用生成器)(这是问题中的想法 2)

def allsets( s , req = set() ):
    if len(req) == len(s):
        if possibleSDRSet( s , req ): yield req
        return
    x = set.union(*s) - req
    if len(x) == 0: return
    xr = x.pop()
    sr = [i - {xr} for i in s]
    for i in allsets( sr , req ): yield i
    for i in allsets( s , req | {xr} ): yield i

这将集合集合和所需元素集合作为参数,然后选择不在所需集合中的元素, 然后它将搜索拆分为没有该元素的集合和具有该元素的集合;第一个方法是创建一个新的集合集合,并从集合中的所有集合中删除元素 第二个是将元素添加到所需的集合中

然而,这些分支中的许多可能不需要考虑,当我们从集合中的所有集合中删除一个元素时,可能不再有 sdr,因此我们可以检查 如果仍然有一个带有 hasSDR 的 sdr,并且只有在存在的情况下继续在该分支中。 当我们将一个元素添加到所需列表时, possibleSDRSet 可能能够排除该分支中存在任何 SDR,因此我们也可以检查这一点,给出

def _findSDRSetsRec( s , req ):
    if len(req) == len(s):
        yield req
        return
    x = set.union(*s) - req
    xr = x.pop()
    sr = [ i - {xr} for i in s ]
    if hasSDR( sr ):
        for i in _findSDRSetsRec( sr , req ): yield i
    reqp = req | {xr}
    if possibleSDRSet( s , reqp ):
        for i in _findSDRSetsRec( s , reqp ): yield i

def findSDRSets(s):
    if not hasSDR(s): return
    for i in _findSDRSetsRec( s , set() ): yield i

这些函数中的第一个假设 s 和 req 已通过两个测试进行检查,因此需要一个包装器来检查是否存在 sdr。 做例子:

s = ({1,3,5,7,9,11,13,15},)*3 +({4,7,8,9,10,12},)*2 + ({1,2,5,6,7,14},)*2
for i in findSDRSets(s): print(i)

如果只有少量有效输出集,使用此方法应该会比您列出的那些提供更快的速度,但是对于给定的示例,allsets 方法看起来更快

【讨论】:

    【解决方案2】:

    我可能会从一个新地图开始,其中包含任何集合中出现的所有数字作为键。对于每个数字,都会注明包含该数字的集合列表。构建这个需要 O(n),而 O 是所有集合中不同数字的总数。

    您需要详尽地找到所有有效的解决方案,这样可能就没有多少优化空间了。算法现在是 O(n!)。

    1. 通过在每个数字上做出一个有效的决定来递归地遍历地图(取数字或不取数字,当你从哪个集合中取数字时)。为此,您可以在参数中跟踪每组还剩下多少个选项。
    2. 现在获得重复解的唯一方法是通过从不同的集合中提取数次来分支。您可以通过在分支时返回信息来进一步减少这种情况(例如,“从集合 3 中获取 5(当前映射键)”分支返回“我也使用了 7、10、11”,然后将该信息提供给下一个调用“获取5(当前地图键)来自 set 7",因此它可能会被修剪。

    修剪更复杂但可行,问题是,是否值得。例如,您在“从 set 7 中取 5”分支中,并且知道 7、10、11 已被另一个分支使用。您可以通过检查第 3 组是否还有任何其他数字来检查您仍然剩下的选择(例如,现在您从第 3 组中还有 3 个选择)是否允许除 7、10、11 之外的其他数字。如果您首先根据数字/键值对集合和地图进行排序,则检查这一点很容易,因为您可以计算集合中高于当前数字的数字(使用有序集合!)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-08-01
      • 2015-06-12
      • 1970-01-01
      • 2016-09-16
      • 2013-11-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多