[英]Finding minimum and maximum in array
这是一个非常基本的算法(无法简单化),但是我很困惑。 我们有一系列元素,必须确定最小值和最大值。
正常方法是遍历数组,找到2n的最小值和最大值进行比较。
稍微更有效的方法是先比较成对的数组的连续元素,以确定任意两个元素的最大值和最小值(n / 2个比较)。 现在,我们有n / 2 min和n / 2 max个元素。 现在我们可以在n / 2 + n / 2 + n / 2(上一步)= 3/2 * n或1.5n中得到最终的最大值和最小值
没关系。 从理论上讲,在第二种情况下,代码应该花更少的时间运行,因为我们进行的比较较少。 但是,当我运行代码时,结果则相反。
我的代码段如下:
public class MinMax {
public static void nonEfficient(int [] array){
int min=array[0],max=array[0];
for (int anArray : array) {
if (anArray < min)
min = anArray;
else {
if (anArray > max)
max = anArray;
}
}
System.out.println("Max is :" + max);
System.out.println("Min is :" + min);
}
public static void efficient(int [] arr,int length){
int max,min;
max = min = arr[0];
int i = 0;
for (; i < length / 2; i++)
{
int number1 = arr[i * 2];
int number2 = arr[i * 2 + 1];
if (arr[i * 2] >= arr[i * 2 + 1])
{
if (number1 > max)
max = number1;
if (number2 < min)
min = number2;
}
else
{
if (number2 > max)
max = number2;
if (number1 < min)
min = number1;
}
}
if (i * 2 < length)
{
int num = arr[i * 2];
if (num > max)
max = num;
if (num < min)
min = num;
}
System.out.println("***********************");
System.out.println("Max is :" + max);
System.out.println("Min is :" + min);
}
public static void main(String[] args) {
int [] array = new int[10000000];
Random rand = new Random();
for(int i=0;i<array.length;i++)
array[i] = rand.nextInt(100000)-144;
long startTime = System.currentTimeMillis();
nonEfficient(array); //theoretically non efficient 2n compares
long stopTime = System.currentTimeMillis();
long elapsedTime = stopTime - startTime;
System.out.println(elapsedTime);// just 11ms
startTime = System.currentTimeMillis();
efficient(array, 10000000);///theoretically more efficient 1.5n compares
stopTime = System.currentTimeMillis();
elapsedTime = stopTime - startTime;
System.out.println(elapsedTime);//whooping 37 ms..what happpened ????
}
}
有人可以帮我弄清楚我在做什么错。 有什么很明显的我想念的东西吗?
谢谢您的帮助。
首先:基准完全有缺陷。 您测量的时间跨度太短,没有JIT的预热,您要在测量中包括System.out.println
的时间。 通过应用“通常的”微基准测试模式,它可能会变得更有意义(请参阅此答案的结尾)
但是即使有了这个“基准”,仍然存在显着差异。 并且有很多可能的原因:
可以假设在现代CPU上进行一次比较需要花费(摊销)一个CPU周期。 一个CPU周期是一束光束传播10厘米所需的持续时间。 现在,测量;-)基本上,算法中的所有其他运算将至少花费相同的时间或更长的时间:每个i++
将花费相同的时间,每个min=x
将花费相同的时间或更长的时间,每个i*2
最有可能需要更长的时间...
另外,我认为这里最重要的一点是:CPU速度很快,但是内存速度却很慢。 在(不是)“效率不高”的情况下,您将依次遍历整个数组。 这非常适合缓存。 每次读取一条缓存行将被充分利用。 与此相反,在(不是)“高效”的情况下,您的内存访问基本上散布在整个阵列上。 它必须将大块数据从主存储器读取到高速缓存中,并且大部分数据将被丢弃,因为不会立即使用它,而是在下一遍再次读取它。
关于基准:可以通过重复几次各自的方法,并取平均时间,然后以增加的数组大小重复进行此操作,使它稍微有意义一些( 大致如下所示):
public static void main(String[] args)
{
Random rand = new Random();
long beforeNS = 0;
long afterNS = 0;
int results[] = { 0, 0 };
int runs = 100;
for (int size=10000; size<=10000000; size*=2)
{
int[] array = new int[size];
for (int i = 0; i < array.length; i++)
array[i] = rand.nextInt(size) - 144;
beforeNS = System.nanoTime();
for (int i=0; i<runs; i++)
{
nonEfficient(array, results);
}
afterNS = System.nanoTime();
System.out.println(
"Not efficient, size "+size+
" duration "+(afterNS-beforeNS)/1e6+
", results "+Arrays.toString(results));
beforeNS = System.nanoTime();
for (int i=0; i<runs; i++)
{
efficient(array, array.length, results);
}
afterNS = System.nanoTime();
System.out.println(
"Efficient , size "+size+
" duration "+(afterNS-beforeNS)/1e6+
", results "+Arrays.toString(results));
}
}
不过,结果可能会受到质疑,ALS只要VM选项是不知道等,但它至少提供了关于是否有这两种方法之间的“显著”差别稍微可靠的指示。
让我们只计算比较:
for (int anArray : array) { // <- One: check array's border
if (anArray < min) // <- Two
min = anArray;
else {
if (anArray > max) // <- Three
max = anArray;
}
}
最后N * 3 = 3N
(在最坏的情况下)
for (; i < length / 2; i++) { // <- One
int number1 = arr[i * 2]; // <- Two
int number2 = arr[i * 2 + 1]; // <- Three
if (arr[i * 2] >= arr[i * 2 + 1]) // <- Five, Six: arr[i * 2]; Seven: arr[i * 2 + 1]
{
if (number1 > max) // <- Eight
max = number1;
if (number2 < min) // <- Nine
min = number2;
}
else
{
if (number2 > max) // <- Eight
max = number2;
if (number1 < min) // <- Nine
min = number1;
}
}
最后: N/2 * 9 = 4.5N
或如果优化程序足够好并且可以消除5、6,
N/2 * 7 = 3.5N
您可以稍微改善代码
int min = array[array.length - 1];
int max = min; // <- there's no need to call array[array.length - 1]
// You can start from array.length - 2 not from usual array.length - 1 here
// Comparison with constant 0 is slightly better
// (one JZ or JNZ assembler instruction)
// than with array.length
for (int i = array.length - 2; i >= 0; --i) {
int v = array[i];
if (v > max)
max = v;
else if (v < min)
min = v;
}
使用Java 8时,我不得不不同意“再简单不过”的说法:
public static void java8(int[] arr, int length){
IntSummaryStatistics stats =
IntStream.of(arr)
.summaryStatistics();
System.out.println("***********************");
System.out.println("Max is :" + stats.getMax());
System.out.println("Min is :" + stats.getMin());
}
这段代码的性能不如您的代码,但是考虑到您还可以获得数组的数量和总数,这还不错。 它将您的代码与java8方法进行比较:
nonEfficient:
Max is :99855
Min is :-144
12
***********************
efficient:
Max is :99855
Min is :-144
43
***********************
java8:
Max is :99855
Min is :-144
69
我将使用JDK进行繁重的工作:
Arrays.sort(array); // sort from lowest to highest
int min = array[0]; // the minimum is now the first element
int max = array[array.length - 1]; // and the maximum is last
完成3行。 除非您要处理大量数组,否则这将非常有效。
您的函数nonEfficient
不正确。 如果用[5, 4, 3, 2, 1]
调用,则else
语句将永远不会执行,并且max
将设置为Integer.MIN_VALUE
。
尝试这个:
if (length & 1)
{
// Odd length, initialize with the first element
min= max= a[0];
i= 1;
}
else
{
// Even length, initialize with the first pair
if (a[0] < a[1])
{
min= a[0]; max= a[1];
}
else
{
min= a[1]; max= a[0];
}
i= 2;
}
// Remaining pairs
while (i < length)
{
int p= a[i++], q= a[i++];
if (p < q)
{
if (p < min) min= p;
if (q > max) max= q;
}
else
{
if (q < min) min= q;
if (p > max) max= p;
}
}
您甚至可以使用这个丑陋的foreach循环摆脱数组索引,但要付出布尔测试和额外开销的代价。
// Initialize
min= max= a[0];
bool Even= true;
int p;
for (int q : a)
{
if (Even)
{
// Set p on hold
p= q;
}
else
{
// Process the pair
if (p < q)
{
if (p < min) min= p;
if (q > max) max= q;
}
else
{
if (q < min) min= q;
if (p > max) max= p;
}
}
Even= !Even;
}
if (!Even)
{
// Process the last element
p= a[length - 1];
if (p < min) min= p;
else
if (p > max) max= p;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.