当使用Java中的列表进行递归时,我经常会多次分配和复制列表。例如,我想生成List<List<Integer>>
所有可能的整数序列,其中:
例如,[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的一个问题有关。
答案 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数组的大小,并传递递归中第一个空闲位置的索引。