查找数组中的最小值和最大值

时间:2014-03-25 06:12:26

标签: java arrays algorithm

这是一个非常基本的算法(不能变得更简单),但我很难过。我们有一系列元素,我们必须确定最小值和最大值。

正常方法是遍历数组并找到min和max,即2n比较。

稍微有效的方法是首先比较对中的数组的连续元素以确定任何两个元素的最大值和最小值(n / 2比较)。我们现在有n / 2分钟和n / 2个最大元素。现在我们可以得到最终的最大值和最小值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 ????
}
}

有人可以帮我弄清楚我做错了什么。是否有一些我很想念的东西。

感谢您的帮助。

7 个答案:

答案 0 :(得分:3)

首先:基准测试完全有缺陷。您测量的时间跨度太短,JIT没有预热,您需要在测量中包括System.out.println的时间。通过应用“通常的”微基准图案模式,可以使稍微更有意义(参见本答案的结尾)

但即使有了这个“基准”,也存在显着差异。这有很多可能的原因:

可以假设现代CPU上的单个比较需要(分摊)单个CPU周期。单个CPU周期是光束为了行进<10>厘米所需的持续时间。现在,测量一下;-)基本上算法中的每个其他操作至少需要相同的时间,或者更长的时间:每i++min=x将花费相同的时间,{{1}会花费相同的时间或更长时间,每个i*2很可能会花费更长的时间......

此外,我认为最重要的一点是:CPU速度很快,但内存很慢。在(非)“不高效”的情况下,您按顺序运行数组。这非常适合缓存。每次读取一个缓存行都将被充分利用。与此相反,在(非)“高效”情况下,您的内存访问基本上分散在整个数组中。它必须从主存储器中读取数据块到缓存中,并且大部分数据必须被丢弃,因为它不会立即使用,但会在下一次传递中再次读取。


关于基准测试:通过多次重复各自的方法,并且花费平均时间,并且随着数组大小的增加而重复执行此操作,可以使稍微更有意义 - 粗略< / em>像这样:

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));
    }
}

尽管如此,结果可能会受到质疑,只要VM选项未知等等。但它至少给出了一个略微更可靠的指示,表明两者之间是否存在“显着”差异方法

答案 1 :(得分:1)

计算比较:

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;
}

答案 2 :(得分:1)

使用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

答案 3 :(得分:0)

我会使用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行。除非您处理大型数组,否则这将非常有效。

答案 4 :(得分:0)

您的功能nonEfficient不正确。如果使用[5, 4, 3, 2, 1]调用它,则else语句将永远不会执行,max将设置为Integer.MIN_VALUE

答案 5 :(得分:0)

如果您使用的是java8,则可以采用流方法 max value using stream

在我看来,它在复杂的结构中更具可读性。 希望有所帮助

答案 6 :(得分:0)

试试这个:

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;
}