草庐IT

java - 三元组的最佳合并

coder 2023-08-29 原文

我正在尝试为以下问题提出一种算法:

我有一组整数的三元组 - 让我们称这些整数为 A、B、C。其中存储的值可能很大,因此通常不可能创建大小为 A、B 或 C 的数组。目标是最小化集合的大小。为此,我们提供了一个简单的规则,允许我们合并三元组:

  • 对于两个三元组 (A, B, C) 和 (A', B', C'),如果 B == B' 和 C = C,则移除原始三元组并放置三元组 (A | A', B, C) ', 哪里 |是按位或。类似的规则也适用于 B 和 C。

  • 换句话说,如果两个三元组的两个值相等,则删除这两个三元组,对第三个值进行按位或运算并将结果放入集合中。

    在类似的情况下,贪婪的方法通常会产生误导,因此对于这个问题,但我找不到一个简单的反例可以找到正确的解决方案。对于包含 250 个项目且正确解为 14 的列表,通过贪婪合并计算的平均大小约为 30(从 20 到 70 不等)。随着列表大小的增加,次优开销变得更大。

    我也尝试过设置位计数,但我没有发现任何有意义的结果。显而易见的事实是,如果记录是唯一的(可以安全地假设),则设置位计数总是增加。

    这是愚蠢的贪婪实现(这只是一个概念上的事情,请不要考虑代码风格):
    public class Record {
        long A;
        long B;
        long C;
    
        public static void main(String[] args) {
            List<Record> data = new ArrayList<>();
            // Fill it with some data
    
            boolean found;
    
            do {
                found = false;
                outer:
                for (int i = 0; i < data.size(); ++i) {
                    for (int j = i+1; j < data.size(); ++j) {
                        try {
                            Record r = merge(data.get(i), data.get(j));
                            found = true;
                            data.remove(j);
                            data.remove(i);
                            data.add(r);
                            break outer;
                        } catch (IllegalArgumentException ignored) {
                        }
                    }
                }
            } while (found);
        }
    
        public static Record merge(Record r1, Record r2) {
            if (r1.A == r2.A && r1.B == r2.B) {
                Record r = new Record();
                r.A = r1.A;
                r.B = r1.B;
                r.C = r1.C | r2.C;
                return r;
            }
            if (r1.A == r2.A && r1.C == r2.C) {
                Record r = new Record();
                r.A = r1.A;
                r.B = r1.B | r2.B;
                r.C = r1.C;
                return r;
            }
            if (r1.B == r2.B && r1.C == r2.C) {
                Record r = new Record();
                r.A = r1.A | r2.A;
                r.B = r1.B;
                r.C = r1.C;
                return r;
            }
            throw new IllegalArgumentException("Unable to merge these two records!");
        }
    

    你知道如何解决这个问题吗?

    最佳答案

    这将是一个很长的答案,遗憾的是没有最佳解决方案(抱歉)。然而,这是将贪婪问题解决应用于您的问题的认真尝试,因此原则上它可能有用。我没有实现讨论的最后一种方法,也许这种方法可以产生最佳解决方案——不过我不能保证。

    0级:不是很贪心

    根据定义,贪心算法具有一种启发式方法,可以以局部最优的方式选择下一步,即当前最优,希望达到全局最优,这可能总是可能也可能不总是可能的。

    您的算法选择任何可合并对并将它们合并,然后继续前进。它不评估此合并意味着什么以及是否有更好的本地解决方案。因此,我根本不会称您的方法为贪婪。这只是一个解决方案,一种方法。我将其称为盲算法,以便我可以在我的回答中简洁地引用它。我还将使用您算法的稍微修改版本,该版本不是删除两个三元组并附加合并的三元组,而是仅删除第二个三元组并将第一个替换为合并的三元组。结果三元组的顺序不同,因此最终结果也可能不同。让我在代表性数据集上运行这个修改后的算法,用 * 标记要合并的三元组。 :

    0: 3 2 3   3 2 3   3 2 3
    1: 0 1 0*  0 1 2   0 1 2
    2: 1 2 0   1 2 0*  1 2 1
    3: 0 1 2*
    4: 1 2 1   1 2 1*
    5: 0 2 0   0 2 0   0 2 0
    
    Result: 4
    

    1级:贪婪

    要使用贪心算法,您需要以一种允许比较选项的方式来制定合并决策,当多个选项可用时。对我来说,合并决策的直观表述是:

    If I merge these two triplets, will the resulting set have the maximum possible number of mergable triplets, when compared to the result of merging any other two triplets from the current set?



    我再说一遍,这对我来说很直观。我没有证据表明这会导致全局最优解,甚至不会导致比盲算法更好或相等的解——但它符合贪婪的定义(并且很容易实现)。让我们在上面的数据集上尝试一下,显示每个步骤之间可能的合并(通过指示三元组对的索引)以及每个可能合并的结果合并数:
              mergables
    0: 3 2 3  (1,3)->2
    1: 0 1 0  (1,5)->1
    2: 1 2 0  (2,4)->2
    3: 0 1 2  (2,5)->2
    4: 1 2 1
    5: 0 2 0
    

    除了合并三元组 1 和 5 之外的任何选择都可以,如果我们采用第一对,我们会得到与盲算法相同的临时集(这次我将折叠索引以消除间隙):
              mergables
    0: 3 2 3  (2,3)->0
    1: 0 1 2  (2,4)->1
    2: 1 2 0
    3: 1 2 1
    4: 0 2 0
    

    这就是这个算法的不同之处:它选择了三元组 2 和 4,因为在合并它们之后仍有一个可能的合并 与盲算法的选择相反 :
              mergables
    0: 3 2 3  (2,3)->0   3 2 3
    1: 0 1 2             0 1 2
    2: 1 2 0             1 2 1
    3: 1 2 1
    
    Result: 3
    

    2级:非常贪婪

    现在,这个直观启发式的第二步是进一步展望一次合并,然后提出启发式问题。概括地说,你会向前看k进一步合并并应用上述启发式,回溯并决定最佳选择。现在这变得非常冗长,所以为了举例说明,我将只执行这一新启发式的一个步骤与前瞻 1:
              mergables
    0: 3 2 3  (1,3)->(2,3)->0
    1: 0 1 0         (2,4)->1*
    2: 1 2 0  (1,5)->(2,4)->0
    3: 0 1 2  (2,4)->(1,3)->0
    4: 1 2 1         (1,4)->0
    5: 0 2 0  (2,5)->(1,3)->1*
                     (2,4)->1*
    

    应用此新启发式方法时,标有星号的合并序列是最佳选择。

    如果需要口头解释:

    Instead of checking how many merges are possible after each possible merge for the starting set; this time we check how many merges are possible after each possible merge for each resulting set after each possible merge for the starting set. And this is for lookahead 1. For lookahead n, you'd be seeing a very long sentence repeating the part after each possible merge for each resulting set n times.



    第 3 级:让我们切掉贪婪

    如果您仔细观察,即使是中等输入和前瞻 (*),先前的方法也具有灾难性的性能。对于超过 20 个三元组的输入,超过 4-merge-lookahead 的任何操作都需要不合理的时间。这里的想法是切断似乎比现有解决方案更糟糕的合并路径。如果我们想要执行前瞻 10,并且特定的合并路径在 3 次合并后产生的可合并量比 5 次合并后的另一条路径少,我们不妨切断当前的合并路径并尝试另一个。这应该可以节省大量时间,并允许进行大的前瞻,这将使我们更接近全局最优解,希望如此。不过,我还没有实现这个进行测试。

    (*): Assuming a large reduction of input sets is possible, the number of merges is proportional to input size, and lookahead approximately indicates how much you permute those merges. So you have choose lookahead from |input|, which is the binomial coefficient that for lookahead ≪ |input| can be approximated as O(|input|^lookahead) -- which is also (rightfully) written as you are thoroughly screwed.



    把这一切放在一起

    我对这个问题很感兴趣,所以我坐下来用 Python 编写了它。可悲的是,我能够证明不同的前瞻可能会产生不同的结果,即使是盲算法偶尔也会比前瞻 1 或 2 更好。这是解决方案不是最佳的直接证明(至少对于 lookahead ≪ |input| ) . See the source code and helper scripts, as well as proof-triplets on github .请注意,除了内存合并结果之外,我没有尝试按 CPU 周期优化代码。

    关于java - 三元组的最佳合并,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22222994/

    有关java - 三元组的最佳合并的更多相关文章

    1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

      很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

    2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

      我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

    3. java - 等价于 Java 中的 Ruby Hash - 2

      我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

    4. ruby - 如果指定键的值在数组中相同,如何合并哈希 - 2

      我有一个这样的哈希数组:[{:foo=>2,:date=>Sat,01Sep2014},{:foo2=>2,:date=>Sat,02Sep2014},{:foo3=>3,:date=>Sat,01Sep2014},{:foo4=>4,:date=>Sat,03Sep2014},{:foo5=>5,:date=>Sat,02Sep2014}]如果:date相同,我想合并哈希值。我对上面数组的期望是:[{:foo=>2,:foo3=>3,:date=>Sat,01Sep2014},{:foo2=>2,:foo5=>5:date=>Sat,02Sep2014},{:foo4=>4,:dat

    5. java - 从 JRuby 调用 Java 类的问题 - 2

      我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

    6. java - 我的模型类或其他类中应该有逻辑吗 - 2

      我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

    7. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

      什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

    8. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

      这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

    9. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

      HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

    10. git使用常见问题(提交代码,合并冲突) - 2

      文章目录git常用命令(简介,详细参数往下看)Git提交代码步骤gitpullgitstatusgitaddgitcommitgitpushgit代码冲突合并问题方法一:放弃本地代码方法二:合并代码常用命令以及详细参数gitadd将文件添加到仓库:gitdiff比较文件异同gitlog查看历史记录gitreset代码回滚版本库相关操作远程仓库相关操作分支相关操作创建分支查看分支:gitbranch合并分支:gitmerge删除分支:gitbranch-ddev查看分支合并图:gitlog–graph–pretty=oneline–abbrev-commit撤消某次提交git用户名密码相关配置g

    随机推荐