什么与 Java 列表相关的微妙之处导致我的堆算法实现失败?

Posted

技术标签:

【中文标题】什么与 Java 列表相关的微妙之处导致我的堆算法实现失败?【英文标题】:What Java List-related subtlety is causing my Heap's Algorithm implementation to fail? 【发布时间】:2020-04-21 00:52:02 【问题描述】:

我正在尝试在 Java 中实现“堆算法”(wiki),它构造给定集合的所有排列。 (我知道这在技术上不是 Heap 的算法,因为 here 指出了一些微妙之处,但目前这对我来说并不是非常重要)。

我所拥有的:我从一段可以工作并做我想做的事情开始:

    public static <T> List<T[]> Permutations(T a[]) 
        LinkedList<T[]> list = new LinkedList<T[]>();
        heapPermutation(a, a.length, a.length, list);
        return list;
    

    //Generating permutation using Heap Algorithm 
    public static <T> void heapPermutation(T a[], int size, int n, List<T[]> list) 
     
        // if size becomes 1 then adds the obtained 
        // permutation to the list
        if (size == 1) 
            list.add(a.clone());

        for (int i=0; i<size; i++) 
         
            heapPermutation(a, size-1, n, list); 

            // if size is odd, swap first and last 
            // element 
            if (size % 2 == 1) 
             
                T temp = a[0]; 
                a[0] = a[size-1]; 
                a[size-1] = temp; 
             

            // If size is even, swap ith and last 
            // element 
            else
             
                T temp = a[i]; 
                a[i] = a[size-1]; 
                a[size-1] = temp; 
             
         
     

    public static void main(String[] args) 
        Character[] chars = new Character[] 'a','b','c','d';

        List<Character[]> list = Permutations(chars);
        for(Iterator<Character[]> i = list.iterator(); i.hasNext();) 
            Character[] array = i.next();
            for(int j = 0; j < array.length; j++) 
                System.out.print(array[j] + " ");
            
            System.out.println();
        
    

再次重申:这段代码可以正常工作并输出我想要的内容。

良好的输出:

a b c d 
b a c d 
c a b d 
a c b d 
b c a d 
c b a d 
d b c a 
b d c a 
c d b a 
d c b a 
b c d a 
c b d a 
d a c b 
a d c b 
c d a b 
d c a b 
a c d b 
c a d b 
d a b c 
a d b c 
b d a c 
d b a c 
a b d c 
b a d c 

我想要什么:我想复制上面的代码,但是用Lists(或ArrayLists、LinkedLists等)替换所有数组实例.

我尝试过的:这是我尝试过的修改:

   public static <T> List<List<T>> Permutations(List<T> a) 
        List<List<T>> list = new ArrayList<List<T>>();
        heapPermutation(a, a.size(), a.size(), list);
        return list;
    

    //Generating permutation using Heap Algorithm 
    public static <T> void heapPermutation(List<T> a, int size, int n, List<List<T>> list) 
     
        List<T> aTemp = new ArrayList<T>(a);

        // if size becomes 1 then adds the obtained 
        // permutation to the list
        if (size == 1) 
            list.add(aTemp);
        

        for (int i=0; i<size; i++) 
         
            heapPermutation(aTemp, size-1, n, list); 

            // if size is odd, swap first and last 
            // element 
            if (size % 2 == 1) 
             
                T temp = aTemp.get(0); 
                aTemp.set(0, a.get(size-1)); 
                aTemp.set(size-1,temp); 
             

            // If size is even, swap ith and last 
            // element 
            else
             
                T temp = aTemp.get(0); 
                aTemp.set(i, a.get(size-1));
                aTemp.set(size-1, temp);
             
         
     

    public static void main(String[] args) 
        List<Character> list = new ArrayList<Character>();
        list.add('a'); list.add('b'); list.add('c'); list.add('d');
        System.out.println(Permutations(list));
    

但是,与第一个代码块不同,这并没有给出我想要的:

输出错误:

[[a, b, c, d], [b, a, c, d], [c, b, a, d], [b, c, a, d], [c, b, c, d], [b, c, c, d], [d, b, c, a], [b, d, c, a], [c, b, d, a], [b, c, d, a], [c, b, c, a], [b, c, c, a], [d, d, c, d], [d, d, c, d], [c, d, d, d], [d, c, d, d], [c, d, c, d], [d, c, c, d], [d, d, d, d], [d, d, d, d], [d, d, d, d], [d, d, d, d], [d, d, d, d], [d, d, d, d]]

第二个代码块中发生了什么导致它不正确?我 100% 确定这是由于我对 Java 中 Lists 的一些微妙之处缺乏了解,但我不知道罪魁祸首是什么。

在这里问之前,我尝试了第二个代码块没有添加List&lt;T&gt; aTemp = new ArrayList&lt;T&gt;(a);,我还尝试修改它的各个部分以将a 的实例更改为aTemp反之亦然。我试过的都没有用。

编辑 1

在下面的评论中,用户@GPI 在我的偶数案例中指出了一个错字。将T temp = aTemp.get(0); 更正为T temp = aTemp.get(i); 后,我有以下输出:

[[a, b, c, d], [b, a, c, d], [c, b, a, d], [b, c, a, d], [c, b, c, d], [b, c, c, d], [d, b, c, a], [b, d, c, a], [c, b, d, a], [b, c, d, a], [c, b, c, a], [b, c, c, a], [d, d, c, b], [d, d, c, b], [c, d, d, b], [d, c, d, b], [c, d, c, b], [d, c, c, b], [d, d, d, c], [d, d, d, c], [d, d, d, c], [d, d, d, c], [d, d, d, c], [d, d, d, c]]

请注意,这也是不正确的,因为它包含许多实际上不是排列的重复项/列表(例如 [d, d, d, c])。

【问题讨论】:

在偶数情况下,T temp = aTemp.get(0); 应该是T temp = aTemp.get(i); @GPI - 感谢您的反馈!实施该更改后(这非常尴尬),我仍然没有得到正确的答案。我会更新说明。 不知道微妙之处,但n不应该以某种方式被使用吗? 数组变体在size == 1 时添加了一个数组克隆,List 变体主要在List&lt;T&gt; a 的深层副本上操作。 @greybeard - 这是我在查找此类代码的 sn-ps 时遇到的可能混淆之一。我的行动计划是获得一个我对 FIRST 感到满意的工作实施,然后然后尝试做一些事情来修复看起来无关的部分。鉴于我什至无法实现功能正确性,我暂时不会对n 感到厌烦。至于您的第二条评论:问题是我 (a) 在 size!=1 案例中没有实现数组克隆,还是 (b) 需要完全做其他事情? 【参考方案1】:

 首先是错字

如前所述,首先要快速解决您的错字:偶数大小写应为 T temp = aTemp.get(i);

二、克隆

问题的症结在于,您没有掌握如何/何时修改列表/数组,以及何时复制它以累积。

甚至不用看你的方法在做什么,我们可以看到数组版本在适当的位置操作数组,除非它的大小为 1,在这种情况下它被复制到结果列表中。

换句话说,在数组版本中,它始终是交换其项目的同一个数组,无论递归有多深。 只有当我们想记住项目的某个排列时,我们才会复制它,以确保排列被冻结 (a.clone()),这意味着我们仍然可以在此之后交换项目,而不会冒对已经累积的排列进行任何修改的风险.

另一方面,列表版本在每次启动时都会复制其输入。换句话说,在每个递归阶段,原始列表的本地副本用于交换项目。当递归展开时,此副本的当前项目排列将丢失。

因此,如果您删除列表的克隆,仅将其保留在 if size == 1) 的情况下,您应该没问题。

最后,列表案例与数组案例之间并没有微妙之处,只是您在没有分析影响的情况下移动了一些“克隆”逻辑。

有了这个,输出变成:

[[a, b, c, d], 
[b, a, c, d],
[c, a, b, d],
[a, c, b, d],
[b, c, a, d],
[c, b, a, d],
[d, b, c, a],
[b, d, c, a],
[c, d, b, a],
[d, c, b, a],
[b, c, d, a],
[c, b, d, a],
[d, a, c, b],
[a, d, c, b],
[c, d, a, b],
[d, c, a, b],
[a, c, d, b],
[c, a, d, b],
[d, a, b, c],
[a, d, b, c],
[b, d, a, c],
[d, b, a, c], 
[a, b, d, c],
[b, a, d, c]]

【讨论】:

感谢您的评论!在我尝试过的大量尝试中,我认为我首先尝试了您正在尝试的东西。我会再次检查并尽快跟进;不过,我和妻子今天必须开车几个小时才能进行化疗输液,所以可能会在今天晚上晚些时候。 我已经更新了输出日志,因此您可以查看。我谨向您和您的家人致以最良好的祝愿。 感谢您的祝福!输液现在正在进行,所以我有五个小时左右的时间要烧掉。 :) 你对重复是绝对正确的!一开始,我确实只有一个重复项(在size==1 的情况下),但我有上述错字。修复错字+修复我所做的愚蠢克隆是完美的。再次感谢!

以上是关于什么与 Java 列表相关的微妙之处导致我的堆算法实现失败?的主要内容,如果未能解决你的问题,请参考以下文章

秘密:从程序员到领导者的微妙之处

调用垃圾回收会导致程序在java中使用更少的堆内存

SQL中exec的微妙之处

Java学习——JSTL标签与EL表达式之间的微妙关系

详解HTTP 与 HTTPS 的不同之处

简单介绍 Java 构造器