繁体   English   中英

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

[英]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

如果您使用的是Java8,则可以使用stream获取流方法最大值

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

尝试这个:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM