在Java中使用列表递归的正确方法是什么?

时间:2009-08-16 01:23:37

标签: java list recursion

当使用Java中的列表进行递归时,我经常会多次分配和复制列表。例如,我想生成List<List<Integer>>所有可能的整数序列,其中:

  • 每个数字介于1到9之间
  • 每个数字都大于或等于它之前的数字
  • 一个数字可以连续出现0到3次。

例如,[1,1,1,2,2,2,3,3,3,4,4,4,5,5,5,6,6,6,7,7,7,8,8,8,9,9,9]是最大的序列。我有一个递归方法来执行此操作:

static void recurse(List<List<Integer>> addto, List<Integer> prev, int n){
    if(n>=10)
        addto.add(prev);
    else{
        for(int i=0; i<=3; i++){
            List<Integer> newlist = new ArrayList<Integer>(prev);
            for(int k=0; k<i; k++){
                newlist.add(n);
            }
            recurse(addto, newlist, n+1);
        }
    }
}

这里发生的是我每次递归都会复制整个prev列表3次。我需要这样做才能将东西连接到我的列表并将其传递给下一个迭代。这很慢(2秒)。使用10个嵌套循环的不太优雅的版本跑得快得多,因为它不需要复制这么多列表。这样做的“正确”方法是什么?

顺便说一下,这不是家庭作业,而是与USACO的一个问题有关。

2 个答案:

答案 0 :(得分:1)

减速可能是由于ArrayLists内部使用的内存被重新分配。默认情况下,ArrayList以10的容量开始。当您添加第11个元素时,它必须扩展它,但它只会扩展50%。此外,当您使用复制构造函数创建ArrayList时,新列表最终会产生更小的容量 - 源列表中的实际元素数加上我认为的10%。 (我猜你的算法的10循环版本只使用了一个“工作”列表,它在添加到List&lt; List&lt; Integer&gt;&gt;)之前就制作了一份副本。

因此,您可以尝试在创建列表时提供容量,并查看是否可以加快速度:

List<Integer> newlist = new ArrayList<Integer>(27);  // longest list size is 9 * 3
newlist.addAll(prev);

编辑:顺便说一下,你应该能够实现一个没有10个嵌套循环的非递归算法。使用堆栈,类似于深度优先树搜索。

答案 1 :(得分:0)

您应该就地修改列表,而不是复制列表,只有在找到解决方案时才复制它。退出递归后,删除列表的最后三个元素。

static void recurse(List<List<Integer>> addto, List<Integer> list, int n){
        if(n>=10)
                addto.add(new ArrayList<Integer>(list));
        else{
                int pos = list.size();
                for(int i=0; i<=3; i++){
                        list.add(n);
                        recurse(addto, newlist, n+1);
                }
                for(int i=2; i>=0; i--){
                        list.remove(i);
                }
        }
}

如果您想获得更高的性能,请尝试使用int [],而不是Integer的ArrayList,因为这将节省您创建的整数。您可以使用27个元素调整list数组的大小,并传递递归中第一个空闲位置的索引。

相关问题