首页 > 解决方案 > 如何在二维数组中找到前 5 个最大值?

问题描述

我有一个二维整数数组。行和列信息(数字的位置)对我很重要。所以,我不想对数组(实际上是矩阵)进行排序。如何从这个二维数组中找到最高的 5 值?

这是我的代码:

for (int row = 0; row < matirx.length; row++) {
  for (int col = 0; col < matirx[row].length; col++) {
    if (matirx[row][col] > maxValue) {
      maxValue = matirx[row][col];
    }
  }
}

标签: javaarraysmax

解决方案


首先,我选择了一个与其他答案非常相似的流解决方案。我不喜欢装箱和拆箱的变体,但由于IntStream没有一种奇特的方法可以Comparator直接开箱即用地进行排序,因此IntStream必须将其转换为 aStream以便以相反的顺序对值进行排序。我认为返回数组并不重要int[],因为我们只对值真正感兴趣。

public static Integer[] streamIt(int[][] matrix, int n){
  Integer[] result = 
    Arrays.stream(matrix)                                         // stream the arrays
          // This is the same as using .flatMaptoInt(..) and then .boxed()
          .flatMap(a -> Arrays.stream(a)                          // stream the array in arrays
                              .mapToObj(i -> Integer.valueOf(i))) // turn the ints into Integers
          .sorted(Comparator.reverseOrder())                      // sort by higest values
          .limit(n)                                               // only pick n
          .toArray(i -> new Integer[i]);                          // put then in Integer array
  return result;
}

如果您希望将它们放在一个int[]数组中,请查看使用do的shadow.sabre的答案。mapToInt()


虽然流解决方案看起来非常整洁,但我觉得问题实际上只是为了获得一组最高值,因此将它们插入到标准 java 排序Set中对我来说是有意义的。我首先将值插入到集合中,直到其中有 5 个元素。然后我检查新值是否高于最低值,如果是,则在插入新值时删除最低值。使用时很容易找到最小值,TreeSet因为它是一个排序集。

诀窍是还要检查新值是否已经在集合中。如果集合中已经有 5、4、3、2、1,并且新值为 5,那么我不想删除最小值 1,因为添加新值实际上不会添加任何新元素集。记住 aSet不能包含重复值:

public static Set<Integer> useSet(int[][] matrix, int n){
  TreeSet<Integer> max = new TreeSet<>(Comparator.<Integer>naturalOrder().reversed());
  for (int i = 0; i < matrix.length; i++) {
    for (int j = 0; j < matrix[i].length; j++) {
      // Keep adding values until there's n elements in the Set
      if (max.size() < n) { 
        max.add(matrix[i][j]);
      } else {
        // if the new value is higher than the lowest value
        //  ..and the new values isn't already there.
        if (max.last() < matrix[i][j] && !max.contains(matrix[i][j])) {
          max.pollLast();
          max.add(matrix[i][j]);
        }
      }
    }
  }
  return max;
}

请注意,此解决方案显然从不包含相同的值,但始终包含顶部不同的值。


查看设置的解决方案,很容易添加额外的功能来跟踪在矩阵中找到值的位置。我创建了一个类 ,Element来包含值及其位置。矩阵中要插入的每个元素TreeSet都创建为Element.

为了对元素进行排序,Element必须使用 a 来初始化其中一个implement Comparable或一个。这个例子两者都有,我只是在实现中使用了. 通常,您会使用 getter 来实现具有私有字段的类来获取值,但为此目的似乎有点冗长。制作字段还可以确保该类是不可变的,因此我对此毫无顾忌。TreeSetComparatorElementstatic ComparatorcompareTo(Element that)Comparable<Element>final

由于使用值和位置进行比较,因此矩阵中的每个元素都是不同的:

class Element implements Comparable<Element> {
  final int value;
  final int x;
  final int y;

  static Comparator<Element> comparator = 
    Comparator.comparing((Element e) -> e.value)
              .thenComparing((Element e) -> e.x)
              .thenComparing((Element e) -> e.y)
              .reversed();

  Element(int value, int x, int y) {
    this.value = value;
    this.x = x;
    this.y = y;
  }

  public int compareTo(Element that){
    return comparator.compare(this, that);
  }

  public String toString(){
    return value + " at [" + x + "][" + y + "]";
  }
}

如果Element没有实现Comparable接口,这将是初始化TreeSet

TreeSet<Element> maxElement = new TreeSet<>(Element.comparator);

但是由于Element确实实现了Comparable接口,因此可以在没有它的情况下初始化 set 实现:

public static Set<Element> useSetElements(int[][] matrix, int n){
  TreeSet<Element> maxElement = new TreeSet<>();
  for (int i = 0; i < matrix.length; i++) {
    for (int j = 0; j < matrix[i].length; j++) {
      if (maxElement.size() < n) {
        maxElement.add(new Element(matrix[i][j],i,j));
      } else {
        if (maxElement.last().value < matrix[i][j]) {
          maxElement.pollLast();
          maxElement.add(new Element(matrix[i][j],i,j));
        }
      }
    }
  }
  return maxElement;
}

请注意,因为每个元素都是不同的,所以不需要检查新值是否已经在集合中。


使用给定的输入运行三个解决方案:

int n = 5;
int[][] matrix = {{16, -20, 22, 19}, 
                  { 2,   5,  6,  8},
                  {17,  25, 16, 19},
                  { 7,  18,  4, 17}};

System.out.println("streamIt: \n "
                   + Arrays.toString(streamIt(matrix,n)));
System.out.println("useSet: \n "
                   + useSet(matrix,n));
System.out.println("useSetElements: \n "
                   + useSetElements(matrix,n));                  

..给出了这个:

streamIt:
 [25, 22, 19, 19, 18]
useSet:
 [25, 22, 19, 18, 17]
useSetElements:
 [25 at [2][1], 22 at [0][2], 19 at [2][3], 19 at [0][3], 18 at [3][1]]

但是性能呢..?

这三种不同的实现让我想知道性能,所以我添加了一个方法来计时执行:

static void timeMethod(Runnable toRun){
  long start = System.nanoTime();
  try{
    toRun.run();
  } finally {
    long end = System.nanoTime();
    System.out.println("  Time: " + (end - start)/1.0e6 + " miliseconds");
  }
}

并运行了三个解决方案:

timeMethod(() -> System.out.println("streamIt: \n "
                                    + Arrays.toString(streamIt(matrix,n))));
timeMethod(() -> System.out.println("useSet: \n "
                                    + useSet(matrix,n)));
timeMethod(() -> System.out.println("useSetElements: \n "
                                    + useSetElements(matrix,n)));

..给出这个结果:

streamIt:
 [25, 22, 19, 19, 18]
  Time: 1.2759 miliseconds
useSet:
 [25, 22, 19, 18, 17]
  Time: 0.9343 miliseconds
useSetElements:
 [25 at [2][1], 22 at [0][2], 19 at [2][3], 19 at [0][3], 18 at [3][1]]
  Time: 1.16 miliseconds

看起来这三种解决方案的性能大致相同。流解决方案似乎稍慢。该Set解决方案看起来很有希望,预计使用该解决方案Element似乎会造成损失。但为了更深入地研究它,我决定在一个更大的矩阵上运行它们,我使用随机整数构建它:

Random random = new Random();
int[][] largerMatrix =
  IntStream.range(0,10000)                     // 10000 on the first dimension
           .mapToObj(i -> random.ints(0,128)   // values between 0 and 128 (not included)
                                .limit(10000)  // 10000 on the second dimension
                                .toArray())    // make the second 1D arrays
           .toArray(int[][]::new);             // put them into a 2D array

使用 10000 x 10000 矩阵运行测试:

timeMethod(() -> System.out.println("streamIt: \n "
                                    + Arrays.toString(streamIt(largerMatrix,n))));
timeMethod(() -> System.out.println("useSet: \n "
                                    + useSet(largerMatrix,n)));
timeMethod(() -> System.out.println("useSetElements: \n "
                                    + useSetElements(largerMatrix,n)));

..给出了这个结果:

streamIt:
 [127, 127, 127, 127, 127]
  Time: 90374.6995 miliseconds
useSet:
 [127, 126, 125, 124, 123]
  Time: 2465.2448 miliseconds
useSetElements:
 [127 at [0][310], 127 at [0][277], 127 at [0][260], 127 at [0][81], 127 at [0][61]]
  Time: 1839.7323 miliseconds

这里的流解决方案似乎非常慢!该Element解决方案是两种Set解决方案的赢家。我希望这是因为Elements 仅在需要插入时才创建,Set并且它正在进行直接int比较,而另一种Set解决方案是在每次比较值时取消装箱。不过,我没有进一步检验我的假设。


我对这个线程中其他解决方案的好奇心也让我测试了这些解决方案。测试的解决方案是:

在小型和大型阵列上运行测试:

System.out.println("--- Testing performance ---");
timeMethod(() -> System.out.println("ArvindKumarAvinash: \n "
                                    + Arrays.toString(ArvindKumarAvinash(matrix,n))));
timeMethod(() -> System.out.println("AnuragJain: \n "
                                    + AnuragJain(matrix,n)));
timeMethod(() -> System.out.println("MichaelChatiskatzi: \n "
                                    + Arrays.toString(MichaelChatiskatzi(matrix,n))));
                                        
System.out.println();
System.out.println("--- Testing performance with largeMatrix---");
timeMethod(() -> System.out.println("ArvindKumarAvinash: \n "
                                    + Arrays.toString(ArvindKumarAvinash(largerMatrix,n))));
timeMethod(() -> System.out.println("AnuragJain: \n "
                                    + AnuragJain(largerMatrix,n)));
timeMethod(() -> System.out.println("MichaelChatiskatzi: \n "
                                    + Arrays.toString(MichaelChatiskatzi(largerMatrix,n))));

..给出了这些结果:

--- Testing performance ---
ArvindKumarAvinash:
 [25, 22, 19, 19, 18]
  Time: 0.9076 miliseconds
AnuragJain:
 [25, 22, 19, 19, 18]
  Time: 6.2277 miliseconds
MichaelChatiskatzi:
 [18, 19, 19, 22, 25]
  Time: 1.2204 miliseconds

--- Testing performance with largeMatrix---
ArvindKumarAvinash:
 [127, 127, 127, 127, 127]
  Time: 3381.1387 miliseconds
AnuragJain:
 [127, 127, 127, 127, 127]
  Time: 120244.7063 miliseconds
MichaelChatiskatzi:
 [127, 127, 127, 127, 127]
  Time: 51.4259 miliseconds

似乎使用流的解决方案根本不是很高效。Michael Chatiskatzi的解决方案是迄今为止性能更好的解决方案。

所有代码

如果你想自己运行它,这里有一个完整的 copy'n'paste'n'run 类:

import java.util.Arrays;
import java.util.Comparator;
import java.util.stream.IntStream;
import java.util.Set;
import java.util.TreeSet;
import java.util.Comparator;
import java.util.Random;
import java.util.List;
import java.util.ArrayList;
import java.util.stream.Collectors;

public class GettingTheTopN {
    public static void main(String[] args) {
      int n = 5;
      int[][] matrix = {{16, -20, 22, 19}, 
                        { 2,   5,  6,  8},
                        {17,  25, 16, 19},
                        { 7,  18,  4, 17}};

      System.out.println("streamIt: \n "
                         + Arrays.toString(streamIt(matrix,n)));
      System.out.println("useSet: \n "
                         + useSet(matrix,n));
      System.out.println("useSetElements: \n "
                         + useSetElements(matrix,n));

      System.out.println();
      System.out.println("--- Testing performance ---");

      timeMethod(() -> System.out.println("streamIt: \n "
                                          + Arrays.toString(streamIt(matrix,n))));
      timeMethod(() -> System.out.println("useSet: \n "
                                          + useSet(matrix,n)));
      timeMethod(() -> System.out.println("useSetElements: \n "
                                          + useSetElements(matrix,n)));
      timeMethod(() -> System.out.println("ArvindKumarAvinash: \n "
                                          + Arrays.toString(ArvindKumarAvinash(matrix,n))));
      timeMethod(() -> System.out.println("AnuragJain: \n "
                                          + AnuragJain(matrix,n)));
      timeMethod(() -> System.out.println("MichaelChatiskatzi: \n "
                                          + Arrays.toString(MichaelChatiskatzi(matrix,n))));

      System.out.println();
      System.out.println("--- Testing performance with largeMatrix---");

      Random random = new Random();
      int[][] largerMatrix =
        IntStream.range(0,10000)                     // 10000 on the first dimension
                 .mapToObj(i -> random.ints(0,128)   // values between 0 and 128 (not included)
                                      .limit(10000)  // 10000 on the second dimension
                                      .toArray())    // make the second 1D arrays
                 .toArray(int[][]::new);             // put them into a 2D array

      timeMethod(() -> System.out.println("streamIt: \n "
                                          + Arrays.toString(streamIt(largerMatrix,n))));
      timeMethod(() -> System.out.println("useSet: \n "
                                          + useSet(largerMatrix,n)));
      timeMethod(() -> System.out.println("useSetElements: \n "
                                          + useSetElements(largerMatrix,n)));
      timeMethod(() -> System.out.println("ArvindKumarAvinash: \n "
                                          + Arrays.toString(ArvindKumarAvinash(largerMatrix,n))));
      timeMethod(() -> System.out.println("AnuragJain: \n "
                                          + AnuragJain(largerMatrix,n)));
      timeMethod(() -> System.out.println("MichaelChatiskatzi: \n "
                                          + Arrays.toString(MichaelChatiskatzi(largerMatrix,n))));
    }

    public static Integer[] streamIt(int[][] matrix, int n){
      Integer[] result = 
        Arrays.stream(matrix)                                         // stream the arrays
              // This is the same as using .flatMaptoInt(..) and then .boxed()
              .flatMap(a -> Arrays.stream(a)                          // stream the array in arrays
                                  .mapToObj(i -> Integer.valueOf(i))) // turn the ints into Integers
              .sorted(Comparator.reverseOrder())                      // sort by higest values
              .limit(n)                                               // only pick n
              .toArray(i -> new Integer[i]);                          // put then in Integer array
      return result;
    }

    public static Set<Integer> useSet(int[][] matrix, int n){
      TreeSet<Integer> max = new TreeSet<>(Comparator.<Integer>naturalOrder().reversed());
      for (int i = 0; i < matrix.length; i++) {
        for (int j = 0; j < matrix[i].length; j++) {
          // Keep adding values until there's n elements in the Set
          if (max.size() < n) { 
            max.add(matrix[i][j]);
          } else {
            // if the new value is higher than the lowest value
            //  ..and the new values isn't already there.
            if (max.last() < matrix[i][j] && !max.contains(matrix[i][j])) {
              max.pollLast();
              max.add(matrix[i][j]);
            }
          }
        }
      }
      return max;
    }

    public static Set<Element> useSetElements(int[][] matrix, int n){
      TreeSet<Element> maxElement = new TreeSet<>();
      for (int i = 0; i < matrix.length; i++) {
        for (int j = 0; j < matrix[i].length; j++) {
          if (maxElement.size() < n) {
            maxElement.add(new Element(matrix[i][j],i,j));
          } else {
            if (maxElement.last().value < matrix[i][j]) {
              maxElement.pollLast();
              maxElement.add(new Element(matrix[i][j],i,j));
            }
          }
        }
      }
      return maxElement;
    }

    // ----------------- Performance

    static void timeMethod(Runnable toRun){
      long start = System.nanoTime();
      try{
        toRun.run();
      } finally {
        long end = System.nanoTime();
        System.out.println("  Time: " + (end - start)/1.0e6 + " miliseconds");
      }
    }

    // [Answer to "How to find first 5 highest value in a two dimensional array?"](https://stackoverflow.com/a/65374950/12695027) by [Arvind Kumar Avinash](https://stackoverflow.com/users/10819573/arvind-kumar-avinash)
    static int[] ArvindKumarAvinash(int[][] matrix, int MAX_N) {
        // Find count as the total number of elements
        int count = 0, row, col;
        for (row = 0; row < matrix.length; row++) {
            count += matrix[row].length;
        }

        // Create flattened = new int[count] and fill it with all elements of matrix[][]
        int[] flattened = new int[count];
        int i = 0;
        for (row = 0; row < matrix.length; row++) {
            for (col = 0; col < matrix[row].length; col++) {
                flattened[i++] = matrix[row][col];
            }
        }

        // Create max = new int[MAX_N] to store maximum n numbers.
        // Also, create maxPos = new int[MAX_N] to store the position of the maximum numbers.
        int[] max = new int[MAX_N];
        int[] maxPos = new int[MAX_N];

        // Loop MAX_N times. In each iteration, assume flattened[0] is the largest number.
        for (i = 0; i < max.length; i++) {
            max[i] = flattened[0];

            for (int j = 1; j < flattened.length; j++) {
                // If flattened[j] >= max[i], check if the position, j has already been
                // processed. If not assign flattened[j] to max[i] and j to maxPos[i].
                if (flattened[j] >= max[i]) {
                    boolean posAlreadyProcessed = false;
                    for (int k = 0; k <= i; k++) {
                        if (maxPos[k] == j) {
                            posAlreadyProcessed = true;
                            break;
                        }
                    }
                    if (!posAlreadyProcessed) {
                        max[i] = flattened[j];
                        maxPos[i] = j;
                    }
                }
            }
        }

        return max;
        // System.out.println("Largest " + MAX_N + " values: " + Arrays.toString(max));
    }

    // [Answer to "How to find first 5 highest value in a two dimensional array?"](https://stackoverflow.com/a/65380541/12695027) by [Anurag Jain](https://stackoverflow.com/users/5825625/anurag-jain)
    static List<Integer> AnuragJain(int[][] matrix, int n) {
      List<Integer> allVal = new ArrayList<>();

     for (int i = 0; i < matrix.length; i++) {
         for (int j = 0; j < matrix[i].length; j++) {
             allVal.add(matrix[i][j]);
         }
     }
      allVal = allVal.stream()
                     .sorted(Comparator.reverseOrder())
                     .limit(n).collect(Collectors.toList());
      return allVal;
      // System.out.println(allVal);
    }

    // [Answer to "How to find first 5 highest value in a two dimensional array?"](https://stackoverflow.com/a/65379921/12695027) by [Michael Chatiskatzi](https://stackoverflow.com/users/11263320/michael-chatiskatzi)
    static int[] MichaelChatiskatzi(int[][] matrix, int n) {
        // int[] highestNumbers = new int[5];
        int[] highestNumbers = new int[n];
        Arrays.fill(highestNumbers, Integer.MIN_VALUE);
        for (int row = 0; row < matrix.length; row++) {
            for (int column = 0; column < matrix[row].length; column++) {
                int currentEntry = matrix[row][column];
                if (currentEntry > highestNumbers[0]) {
                    highestNumbers[0] = currentEntry;
                    Arrays.sort(highestNumbers);
                }
            }
        }
        return highestNumbers;
        // System.out.println(Arrays.toString(highestNumbers));
    }
}


// -------------------------------------------
// -------------------------------------------
class Element implements Comparable<Element> {
  final int value;
  final int x;
  final int y;

  static Comparator<Element> comparator = 
    Comparator.comparing((Element e) -> e.value)
              .thenComparing((Element e) -> e.x)
              .thenComparing((Element e) -> e.y)
              .reversed();

  Element(int value, int x, int y) {
    this.value = value;
    this.x = x;
    this.y = y;
  }

  public int compareTo(Element that){
    return comparator.compare(this, that);
  }

  public String toString(){
    return value + " at [" + x + "][" + y + "]";
  }
}

推荐阅读