日常开发中,我们经常会遇到类似这样的嵌套循环:
for (int i = 0; i < outterLoopCount; i++)
for (int j = 0; j < innerLoopCount; j++)
doSomething();
一般的说法是把循环次数少的循环放在外面,如jdk里java.util.Collections.disjoint(Collection> c1, Collection> c2)的实现.
public static boolean disjoint(Collection> c1, Collection> c2) {
/*
* We're going to iterate through c1 and test for inclusion in c2.
* If c1 is a Set and c2 isn't, swap the collections. Otherwise,
* place the shorter collection in c1. Hopefully this heuristic
* will minimize the cost of the operation.
*/
if ((c1 instanceof Set) && !(c2 instanceof Set) ||
(c1.size() > c2.size())) {
Collection> tmp = c1;
c1 = c2;
c2 = tmp;
}
for (Object e : c1)
if (c2.contains(e))
return false;
return true;
}
这种方法是出于减少循环次数的目的,能减少 (大循环长度 - 小循环长度)次循环,但这样能保证效率总是最优吗?
static void loop() {
int[][] arr = new int[1000000][10];
// 第一个循环,大循环放在里面
long start = System.currentTimeMillis();
for (int i = 0; i < 10; i++)
for (int j = 0; j < 1000000; j++)
arr[j][i] = i + j;
long loop1End = System.currentTimeMillis();
// 第二个循环,大循环放在外面
for (int i = 0; i < 1000000; i++)
for (int j = 0; j < 10; j++)
arr[i][j] = i + j;
long loop2End = System.currentTimeMillis();
System.out.println(loop1End - start);
System.out.println(loop2End - loop1End);
}
在我的机器上运行,第二个循环总是比第一个循环快。
所以将循环次数少的循环放在外面只考虑到了循环次数,并没有考虑到单次循环花费的时间,并不总是正确的优化方法。
那么怎样才对正确的优化循环呢?我首先想到的是对其建立模型求解。
有两个循环loop1, loop2,次数为分别为 x,y ,单次循环时间分别为 a,b;
设x > y >= 0;
a >= 0;
b >= 0;
解:
设 n = x - y,则 n >0 且为常数, y = x - n;
两种组合花费的时间分别为:
loop1 在外,loop2 在内: x(a + b * y) #1式
loop1 在内,loop2 在外:y(b + a * x) #2式
时间差t为:
t = #1 - #2
t = (b - a) x^2 + (n + 1)(a - b)x + bn
(1) 当a==b时,t是常量bn;
(2) 当a<>b时,是一个二次函数,根据我们设置的条件,不能得到惟一的函数图形。
其顶点为: ((n + 1) / 2, (a -b)(n + 1) ^ 2 / 4);
落在t轴上的坐标为: (0, bn);
落在x轴上的坐标为: 1/2的 (n + 1) ^ 2 + 4bn / (a - b) 平方根 - (n +1);
说到最后,依然无法得出指导性的优化原则,当然了,一般编程时并不需要去考虑这种问题,对代码效率影响太小了,并没有什么太多的实际意义。