3

给定一个偶数 (2k) 个元素的列表 L,我正在寻找一种算法来生成具有以下属性的 2k-1 个子列表的列表:

  1. 每个子列表恰好包含来自 L 的元素的 k 2-组合(顺序无关紧要的对),
  2. 每个子列表只包含 L 中的每个元素一次,并且
  3. 所有子列表中所有元素的并集恰好是 L 中所有可能的 2 组合的集合。

例如,如果输入列表是 L = [a, b, c, d],我们有 k = 2 和 3 个子列表,每个子列表包括 2 对。一个可能的解决方案类似于 [[ab, cd], [ac, bd], [ad, bc]]。如果我们忽略列表中所有元素的排序(将所有列表视为集合),事实证明这也是 k = 2 的唯一解决方案。

我现在的目标不仅是找到一个单一的解决方案,而且是所有可能的解决方案。随着所涉及组合的数量快速增长,最好以巧妙的方式构建所有结果,而不是生成大量候选列表并从中删除不满足给定属性的元素。这样一个朴素的算法可能如下所示:

  1. 求 L 的所有 2-组合的集合 C。
  2. 求 C 的所有 k 组合的集合 D。
  3. 从 D 中选择所有并集等于 L 的集合,称为新集合 D'。
  4. 找到 D' 的所有 (2k-1) 组合的集合 E。
  5. 从 E 中选择所有集合,联合是集合 C,并让新集合作为最终输出。

这个算法很容易实现,但是对于更大的输入列表来说速度非常慢。那么有没有办法更有效地构建结果列表呢?


编辑:这是 L = [a,b,c,d,e,f] 的结果,k = 3,由上述算法计算得出:

[[[ab,cd,ef],[ac,be,df],[ad,bf,ce],[ae,bd,cf],[af,bc,de]],
 [[ab,cd,ef],[ac,bf,de],[ad,be,cf],[ae,bc,df],[af,bd,ce]],
 [[ab,ce,df],[ac,bd,ef],[ad,be,cf],[ae,bf,cd],[af,bc,de]],
 [[ab,ce,df],[ac,bf,de],[ad,bc,ef],[ae,bd,cf],[af,be,cd]],
 [[ab,cf,de],[ac,bd,ef],[ad,bf,ce],[ae,bc,df],[af,be,cd]],
 [[ab,cf,de],[ac,be,df],[ad,bc,ef],[ae,bf,cd],[af,bd,ce]]]

满足所有性质:

  1. 每个子列表有 k = 3 2-组合,
  2. 每个子列表仅包含每个元素一次,并且
  3. 一个解决方案的所有 2k-1 = 5 个子列表的并集恰好是 L 的所有可能的 2 组合的集合。

编辑 2:根据 user58697 的回答,我使用循环锦标赛调度改进了计算算法:

  1. 令 S 为结果集,从一个空集开始,P 为 L 的所有排列的集合。
  2. 重复以下操作,直到 P 为空:
    • 从 P 中选择任意排列
    • 为此排列执行完整的 RRT 调度。在每一轮中,来自 L 的元素的排列形成 L 的一个排列。从 P 中移除所有这 2k 个排列。
    • 将生成的计划添加到 S。
  3. 如果它们的子列表的并集具有重复元素(即不加起来为 L 的所有 2 组合),则从 S 中删除所有列表。

这个算法比第一个算法性能好得多。我能够将 k = 4 的结果数计算为 960,将 k = 5 的结果数计算为 67200。不过,这个序列似乎没有OEIS 结果的事实让我想知道这些数字是否真的正确,即如果算法正在生成完整的解决方案集。

4

2 回答 2

2

这是一个循环赛的日程安排:

  1. 一对是一对,
  2. 列表是一轮(每支球队与其他球队一起比赛)
  3. 一组列表是一个完整的锦标赛(每支球队只与对方球队交手一次)。

看看这里

于 2017-02-08T18:23:15.093 回答
1

这是一个有趣的问题。在回答的过程中(基本上是在写完下面包含的程序,并在OEIS上查找序列之后),我了解到这个问题有一个名字和丰富的理论:你想要的是生成完整图的所有1-factorizations K 2k


让我们首先用该语言重述问题:

给你一个数字 k,和一个大小为 2k 的列表(集合)L。我们可以将 L 视为完整图 K 2k的顶点集。

  • 例如,当 k=3 时,L 可以是 { a , b , c , d , e , f }

1因子(又名完美匹配)是将 L 划分为无序对(大小为 2 的集合)。也就是说,它是一组 k 对,其不相交并集为 L。

  • 例如,ab - cd - ef是 L = { a , b , c , d , e , f } 的一因子。这意味着ab匹配,cd匹配,ef匹配。这样,L 被划分为三个集合 { a , b }, { c , d } 和 { e , f },它们的并集是 L。

令S(在问题中称为C)表示L的所有元素对的集合。(就完整图而言,如果L是它的顶点集,S是它的边集。)注意S包含(2k选择2 ) = k(2k-1) 对。所以对于 k = 0, 1, 2, 3, 4, 5, 6…, S 的大小为 0, 1, 6, 15, 28, 45, 66…</a>。

  • 例如,S = { ab , ac , ad , ae , af , bc , bd , be , bf , cd , ce , cf , de , df , ef } 对于我们上面的 L (k = 3, 所以 |S| = k(2k-1) = 15)。

1因子分解是将 S 划分为集合,每个集合本身都是 1 因子(完美匹配)。请注意,由于这些匹配中的每一个都有 k 对,并且 S 的大小为 k(2k-1),因此分区的大小为 2k-1(即,由 2k-1 个匹配组成)。

  • 例如,这是一个 1-factorization:{ ab - cd - ef , ac - be - df , ad - bf - ce , ae - bd - cf , af - bc - de }

换句话说,S 的每个元素(每对)恰好出现在 1 因子分解的一个元素中,而 L 的每个元素恰好出现在 1 因子分解的每个元素中一次。

该问题要求生成所有 1 分解。


令 M 表示 L 的所有 1 因子(所有完美匹配)的集合。很容易证明 M 包含 (2k)!/(k!2^k) = 1×3×5×…×(2k- 1)匹配。对于 k = 0, 1, 2, 3, 4, 5, 6…, M 的大小为1, 1, 3, 15, 105, 945, 10395…</a>。

  • 例如,对于我们上面的 L,M = { ab - cd - ef , ab - ce - df , ab - cf - de , ac - bd - ef , ac - be - df , ac - bf - de , ad - bc -efad - be - cfad - bf - ceae _- bc - df , ae - bd - cf , ae - bf - cd , af - bc - de , af - bd - ce , af - be - cd } (对于 k=3,这个数字 15 与对,但这只是一个巧合,你可以从其他数字中看出:这个数字的增长速度比对的数量要快得多。)

M 很容易生成:

def perfect_matchings(l):
    if len(l) == 0:
        yield []
    for i in range(1, len(l)):
        first_pair = l[0] + l[i]
        for matching in perfect_matchings(l[1:i] + l[i+1:]):
            yield [first_pair] + matching

例如,调用会按预期perfect_matchings('abcdef')生成 15 个元素。['ab', 'cd', 'ef'], ['ab', 'ce', 'df'], ['ab', 'cf', 'de'], ['ac', 'bd', 'ef'], ['ac', 'be', 'df'], ['ac', 'bf', 'de'], ['ad', 'bc', 'ef'], ['ad', 'be', 'cf'], ['ad', 'bf', 'ce'], ['ae', 'bc', 'df'], ['ae', 'bd', 'cf'], ['ae', 'bf', 'cd'], ['af', 'bc', 'de'], ['af', 'bd', 'ce'], ['af', 'be', 'cd']

根据定义,1 因子分解是将 S 划分为 M 中的元素。或者等效地,M 的任何 (2k-1) 个不相交元素形成 1 因子分解。这适用于一个简单的回溯算法:

  • 从一个空列表开始(部分分解)
  • 对于完美匹配列表中的每个匹配,尝试将其添加到当前的部分分解中,即检查它是否不相交(它不应包含任何已使用的对)
    • 如果没问题,将其添加到部分分解中,并尝试扩展

在代码中:

matching_list = []
pair_used = defaultdict(lambda: False)
known_matchings = []  # Populate this list using perfect_matchings()
def extend_matching_list(r, need):
    """Finds ways of extending the matching list by `need`, using matchings r onwards."""
    if need == 0:
        use_result(matching_list)
        return
    for i in range(r, len(known_matchings)):
        matching = known_matchings[i]
        conflict = any(pair_used[pair] for pair in matching)
        if conflict:
            continue  # Can't use this matching. Some of its pairs have already appeared.
        # Else, use this matching in the current matching list.
        for pair in matching:
            pair_used[pair] = True
        matching_list.append(matching)
        extend_matching_list(i + 1, need - 1)
        matching_list.pop()
        for pair in matching:
            pair_used[pair] = False

如果您使用extend_matching_list(0, len(l) - 1)(在填充后known_matchings)调用它,它会生成所有 1-factorizations。我已将执行此操作的完整程序放在这里。当 k=4 (特别是 list 'abcdefgh')时,它输出 6240 个 1-factorizations;完整的输出在这里


正是在这一点上,我将序列 1、6、6240 输入到 OEIS,并发现了OEIS A000438,序列 1、1、6、6240、1225566720、252282619805368320,...</a>。它表明,对于 k=6,解的数量≈2.5×10 17意味着我们可以放弃生成所有解的希望。即使对于 k=5,约 10 亿个解决方案(回想一下,我们正试图从 |M|=945 个匹配项中找到 2k-1=9 个不相交的集合)将需要一些精心优化的程序。

第一个优化(令人尴尬的是,我后来通过仔细查看 k=4 的跟踪输出才意识到)是(在自然字典编号下)分区中选择的第一个匹配的索引不能大于匹配的数量k-1。这是因为 S 的字典序上的第一个元素(如“ ab ”)只出现在那些匹配中,如果我们晚于这个开始,我们将永远不会在任何其他匹配中再次找到它。

第二个优化来自这样一个事实,即回溯程序的瓶颈通常是测试当前候选人是否可以接受。我们需要有效地测试不相交性:给定的匹配(在我们的部分分解中)是否与所有先前匹配的并集不相交。(它的任何 k 对是否是之前匹配已经覆盖的一对。)对于 k=5,结果 S 的大小,即 (2k 选择 2) = 45,小于 64,所以我们可以紧凑地表示一个 64 位整数类型的匹配(毕竟是 S 的一个子集):如果我们将这些对编号为 0 到 44,那么任何匹配都可以用一个在元素对应的位置上具有 1 的整数来表示它包含。然后测试不相交是对整数的简单按位运算:

执行此操作的 C++ 程序在此处,并且只是回溯部分(专门用于 k=5)不需要任何 C++ 功能,因此在此处将其提取为 C 程序。它在我的笔记本电脑上运行大约 4-5 小时,并找到所有 1225566720 1-factorizations。

看待这个问题的另一种方法是,如果 M 的两个元素相交,则它们之间有一条边(有一对(S 的元素)共同),并且我们正在寻找 M 中的所有最大独立集。同样,解决该问题的最简单方法可能仍然是回溯(我们将编写相同的程序)。

通过利用问题中的对称性,我们的程序可以变得更加高效:例如,我们可以选择任何匹配作为 1 因子分解中的第一个 1 因子(然后通过重新标记生成其余部分,注意不要避免重复)。这就是计算 K 12(当前记录)的 1 分解数的方式。


关于生成所有解决方案的智慧的说明

The Art of Computer Programming第 4A 卷中,在第 7.2.1.2 节生成所有排列的末尾,Knuth 有一条重要的建议:

三思而后行。在本节中,我们已经看到了几种有吸引力的置换生成算法,但是许多算法是已知的,通过这些算法可以找到对特定目的最优化的置换,而无需遍历所有可能性。例如,[…] 在顺序存储中排列记录的最佳方式 […] 只需要 O( n log n ) 步骤。[…]分配问题,它询问如何置换方阵的列,以使对角线元素的和最大化 […] 最多可以在 O( n 3 ) 次操作中解决,所以这样做是愚蠢的使用n阶方法!除非n非常小。即使在旅行销售代表问题这样的情况下,当没有已知的有效算法时,我们通常可以找到比检查所有可能的解决方案更好的方法。当有充分的理由单独查看每个排列时,最好使用排列生成。

这似乎是这里发生的事情(来自问题下方的评论):

我想计算所有解决方案以在这些解决方案上运行不同的属性指标并找到一个可选的匹配项 [...]。由于结果的数量似乎比预期的增长更快,这是不切实际的。

通常,如果您尝试“生成所有解决方案”并且您没有很好的理由来查看每个解决方案(几乎从来没有),那么还有许多其他方法更可取,从直接尝试到解决优化问题,生成随机解决方案并查看它们,或从某个子集生成解决方案(这似乎是您所做的)。


进一步阅读

对OEIS的后续参考产生了丰富的历史和理论。

  • 关于完整图的 1 分解以及与循环调度的关系,Gelling(MA 论文),1973

  • 关于完整图的 1 因子分解的数量,Charles C Lindner、Eric Mendelsohn、Alexander Rosa(1974?)——这表明K 2n上的非同构1 因子分解的数量随着 n 趋于无穷大而趋于无穷大。

  • E. 门德尔松和 A. 罗莎。关于完全图的一分解的一些性质。大会。编号,24(1979):739–752

  • E. 门德尔松和 A. 罗莎。完整图的一个分解:一项调查。Journal of Graph Theory, 9 (1985): 43–65 (早在 1985 年,这个确切的问题就已经被研究得很好,以至于需要进行调查!)

  • 通过Dinitiz 的论文

    • DK Garnick 和 JH Dinitz,关于 12 点上完整图的一分解数,Congressus Numerantium,94 (1993),第 159-168 页。他们宣布他们正在计算 K 12的非同构 1 因式分解的数量。他们的算法基本上是回溯。
    • Jeffrey H. Dinitz、David K. Garnick、Brendan D. McKay:K12 有 526,915,620 个非同构单因式分解(也在此处),Journal of Combinatorial Designs 2 (1994),第 273 - 285 页:他们完成了计算,并且报告了他们为 K 12找到的数字(526,915,620 个非同构,总共 252,282,619,805,368,320 个)。
  • Gopal、Kothapalli、Venkaiah、Subramanian(2007 年)的完整图的各种单因子分解。一篇与这个问题相关的论文,并且有很多有用的参考资料。

  • WD Wallis,组合设计简介,第二版(2007 年)。第 10 章是“单因式”,第 11 章是“单因式的应用”。两者都非常相关,并且有许多有用的参考。

  • Charles J. Colbourn 和 Jeffrey H. Dinitz,组合设计手册,第二版(2007 年)。一座金矿。参见章节 VI.3 平衡比赛设计,VI.51 安排比赛,VII.5 图分解(包括其第 5.4 节枚举和表格,5.5 完整图的一些 1-分解),VII.6 设计理论中的计算方法( 6.2 穷举搜索)。最后一章参考:

    • [715] K 12是如何计算的(“有序算法”),一种回溯——上面提到的 Dinitz-Garnick-McKay 论文
    • [725] “在与分解相关的许多其他主题中,包含一种快速算法,用于查找 K 2n的 1-分解。” (“房间广场和相关设计”,JH Dinitz 和 SR Stinson)
    • [1270](P. Kaski 和 PRJ Östergård,12 阶正则图的单因式分解,Electron. J. Comb. 12,Research Paper 2,25 pp. (2005))
    • [1271] “包含电子形式的完整图形的 1 分解,最高可达 10 阶。” (P. Kaski 和 PRJ Östergård,代码和设计的分类算法,Springer,柏林,2006 年。)
    • [1860] “关于 K 2n完美 1 分解的调查”(ES Seah,完全图的完美单分解——调查,Bull. Inst. Combin. Appl. 1 (1991) 59-70)
    • [2107] “对完整图的 1 因式分解的调查,包括本章的大部分材料。” WD Wallis,完整图的单因式分解,Dinitz 和 Stinson(编辑),当代设计理论,1992
    • [2108] “一本关于图的 1 因式分解的书。” WD Wallis,“单因式”,Kluwer,Dordrecht,1997

其他一些东西:

于 2017-02-12T08:32:31.370 回答