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

Posted

技术标签:

【中文标题】在数组中查找最小值和最大值【英文标题】:Finding minimum and maximum in array 【发布时间】:2014-03-25 06:12:25 【问题描述】:

这是一个非常基本的算法(再简单不过了),但我很难过。我们有一个元素数组,我们必须确定最小值和最大值。

通常的方法是遍历数组并找到 2n 次比较的 min 和 max。

更有效的方法是首先成对比较数组的连续元素以确定任意两个元素的最大值和最小值(n/2 比较)。我们现在有 n/2 min 和 n/2 max 元素。现在我们可以在 n/2 + n/2 + n/2(上一步) = 3/2* n 或 1.5n 比较中得到最终的最大值和最小值

没关系。从理论上讲,在第二种情况下运行代码应该花费更少的时间,因为我们进行的比较更少。但是当我运行代码时,结果却不然。

我的代码 sn-p 如下:

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 ????


有人可以帮助我弄清楚我做错了什么。有什么我很明显的遗漏吗?

感谢您的帮助。

【问题讨论】:

我的第一个赌注是如果检查您是否采用“高效”方法。不是使用 number1 和 number2,而是再次搜索数组。当然,我确信每个 java 的优化水平比代码所暗示的要低得多,所以你可能也失去了很多效率。 &gt; 这样的显式比较并不是例程中唯一的耗时操作。访问像arr[i * 2] 这样的数组可以比较检查数组的边界。 不。我认为这不会带来如此巨大的利润。 尝试使用max 函数的简单方法。我可以想象编译器会优化那个(SSE) @TapasKumarSenapati 好吧,显然你错了,正如你自己的基准测试显示的那样...... 【参考方案1】:

首先:基准测试完全有缺陷。您测量的时间跨度太短,没有任何 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));
    

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

【讨论】:

+1:当您必须查看每个项目时,更少的代码字节和顺序内存访问很难被击败。关于时间的好信息。 很好的推理马可。我将接受这个答案作为最合理的解释。【参考方案2】:

在使用 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】:

让我们计数比较:

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;

【讨论】:

你为什么把内存访问算作比较? 在“arr[i * 2]”编译器应该检查“i * 2”是否在数组边界范围 [0..length - 1] 内。 这是有道理的。隐式循环结构避免了这种边界检查(幸运的是,可以在一次比较中完成),给它一个很大的优势。【参考方案4】:

我会使用 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 行就完成了。除非您正在处理巨大的数组,否则这将非常有效。

【讨论】:

什么?即使对于n=1000000,由于不必要的日志因素,这应该很多 @NiklasB。我会称 100 万个元素“巨大” 但这就是 OP 正在测试的。即使对于n=1000,这个因素也可能很明显 当然,我是在具有 100 万个元素的 OPs 基准环境中对此进行评估的 对可以在O(N) 中解决的问题使用O(N.Log(N)) 解决方案显然效率低下。【参考方案5】:

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

【讨论】:

不,设置为arr[0],确实是最大值。 @YvesDaoust -- 在我回复后对帖子进行了编辑。最初的实现没有将minmax设置为第一个元素,而是分别设置Integer.MAX_VALUEInteger.MIN_VALUE 我明白了。这确实是一个微妙的错误。我记得它在 80 年实现表面绘图算法时让我印象深刻;-)【参考方案6】:

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

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

【讨论】:

【参考方案7】:

试试这个:

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;

【讨论】:

以上是关于在数组中查找最小值和最大值的主要内容,如果未能解决你的问题,请参考以下文章

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

Javascript:使用 reduce() 查找最小值和最大值?

从 p5.js 的 JSON 文件中的数组中查找最小值和最大值

使用 ARM NEON 指令查找数组的最小值和最大值

Integer.MAX_VALUE和Integer.MIN_VALUE的解释,用于查找数组中的最小值和最大值

c_cpp 使用分而治之的方法查找未排序数组中的最小值和最大值