【问题标题】:Finding all exact covers of size k找到大小为 k 的所有精确覆盖
【发布时间】:2023-02-02 07:46:02
【问题描述】:

我正在寻找有关如何解决以下问题的一些想法。我的主要语言是 R。

我有一套小号和有效子集的集合ü.我想找到所有确切的封面小号ü完全使用k子集。

例如

  • 设置小号= {1,2,3,4}
  • 有效子集ü= {{1,2,3,4},{1,2},{3,4},{1,4},{2,3},{1},{4}}
  • 什么时候k= 1 有 1 个解 {1,2,3,4}
  • 什么时候k= 2 有 2 个解 {{{1,2}{3,4}},{{1,4}{2,3}}}
  • 什么时候k= 3 有 1 个解
  • 什么时候k>= 4 没有解决方案

在我现实生活中的例子中小号有 500 个元素和ü有 500,000 个子集。每个子集有 1 到 8 个元素。使用线性程序,我发现最小的精确封面大小为 70。我正在寻找所有大小为 70 的封面。理论上,我可以循环线性程序,为现有解决方案添加约束,以找到新的解决方案。我怀疑这会很慢。

如果深度大于k.这适用于较小的示例,但似乎陷入了更深入的搜索。我可以通过切换到 C++ 或使用更高级的数据结构(例如 ZDD)来进行一些改进。

任何替代方法的建议将不胜感激。

下面的代码是我如何使用线性规划找到最小覆盖

library(Rsymphony)

mat #sparse matrix of 1s with dimensions 500 x 500,000
dir <- rep("==",500)
rhs <- rep(1,500)
types <- rep("B",500000)
score <- rep(-1,500000)
max <- T

soln <- Rsymphony_solve_LP(score,mat,dir,rhs,max = max,types = types)

【问题讨论】:

  • 如果您分享目前拥有的代码,您更有可能在此处获得支持
  • 添加了一些代码来展示我如何获得一个最小的封面。找到 1 花了 15 分钟。我想找到所有其他的那个大小。我知道有多个,根据数据的性质,我怀疑它远小于 500k。

标签: r algorithm combinatorics


【解决方案1】:

你在你的问题中提到你愿意接受与 Dancing Links 实现相关的建议并切换到 C++ 作为替代方案,所以我将提出与该替代方案相关的建议。

您提到您已经通过 Dancing Links 实施了 Knuth 的算法 X。因此,如果没有代码,我会相信你已经正确地实现了它,并且只是提醒你一些可以加快速度的关键优化。

  1. 确保您的选择启发式选择了一个项目来覆盖它在所有可用选项中最难访问的项目。在下图中,我选择了第一个遇到的、最难访问的项目来尝试覆盖k= 2 为了找到两个总数的一个可能的解决方案。这可以加快速度。选择的项目和用于覆盖它的选项都标有红色星号。

    1. 在选择要涵盖的项目时,还要确保检查所有剩余项目,看是否有任何因过去选择的选项而无法访问的项目。如果一个项目在选项中的出现次数为零,你应该停止在该分支下递归。这将修剪您的递归搜索并阻止您继续探索无意义的分支,同时加快速度。

    2. 考虑 Knuth 最近对 Dancing Links 实际实现的修改,并将链接放在数组或向量中,而不是作为网格中的四重链接节点。您可以在易于调试、内存安全并且提供出色的局部性/缓存友好性的数据结构中获得相同的功能。在 C++ 中,这里是我可能如何布置您的问题以及跳舞链接如何出现在内存中。

    struct header {
        int title;
        int left;
        int right;
    };
    struct link {
        int topOrLen;
        int up;
        int down;
    };
    /* This vector controls recursion and is how you 
     * decide what item to attempt to cover.
     */
    std::vector<header> lookupTable = {
        {-1, 4, 1},
        {1, 0, 2},
        {2, 1, 3},
        {3, 2, 4},
        {4, 3, 0},
    };
    /* The top row tracks the number of appearances of a given item.
     * Row spacers point to the first item in the previous row and 
     * last item in the current row. Links in each row point above
     * and below--left and right is implicit--and always point back
     * up to the column header.
     */
    std::vector<link> dancingLinks = {
        // 0          1         2         3         4
        {0,0,0},    {4,23,6}, {3,20,7}, {3,21,8}, {4,25,9},
        // 5          6-1       7-2       8-3       9-4
        {-1,0,9},   {1,1,11}, {2,2,12}, {3,3,14}, {4,4,15},
        // 10         11-1      12-2
        {-2,6,12},  {1,6,17}, {2,7,20},
        // 13                             14-3      15-4
        {-3,11,15},                     {3,8,21}, {4,9,20},
        // 16         17-1                          18-4
        {-4,14,18}, {1,11,23},                    {4,15,25},
        // 19                   20-2      21-3
        {-5,17,21},           {2,12,2}, {4,14,3},
        // 22         23-1
        {-6,20,23}, {1,17,1},
        // 24                                       25-4
        {-7,23,25},                               {4,18,4},
        // 25
        {INT_MIN,25,INT_MIN}
    };
    

    请注意,在 Knuth 的计算机编程艺术:第 4B 卷:组合算法,第 2 部分, Knuth 提到这种数组方法对减少内存访问次数没有太大作用,或者记忆体与链接节点相比,他将它们称为算法。但是,我仍然认为提到的好处是值得的。不过,这仍然是您描述的一个难题。

    最后,如果您想了解如何在 C++ 中通过 Dancing Links 实现算法 X,我在这里使用该技术解决了一个问题:dancing-links-and-planning-pokemon。这是一个愚蠢的编造问题,但它可能会给您一些切换到 C++ 的想法,并且确切的覆盖实现忠实于 Knuth 的算法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-03-29
    • 1970-01-01
    • 2015-09-19
    • 1970-01-01
    • 1970-01-01
    • 2016-02-25
    • 2021-04-11
    • 2018-05-01
    相关资源
    最近更新 更多