Java: Collection 个最高的 N 个元素

Java: Collection of N highest elements

我需要在一大 collection 数据中找到 N 个最大的元素。

我有:

我需要:

我的做法:

这个任务的最佳解决方案是什么?

对此有一些想法。

此任务非常适合使用多个处理器。您可以将页面拆分到线程池中,然后在完成时合并结果。

不需要插入每个值,让集合排序然后删除最小值。更快的方法是检查每个项目是否大于集合中最小的(即最后一个)项目。

下面是一个简单的示例,它在包含 10,000 个随机整数的数组中查找 100 个最大的整数。

    Queue<Integer> largest = new PriorityQueue<>(100);

    for (int item : new Random().ints(10000, 0, 100).toArray()) {
        if (largest.size() < 100 || largest.peek() < item) {
            if (largest.size() == 100)
                largest.remove();
            largest.add(item);
        }
    }
    System.out.println(largest);

我会用 List<Item> 替换你的 largest。在你的循环中你可以做类似的事情:

largest.add(current);
bubbleSort(largest);
if ( largest.size() > 100 ) {
  largest.remove(0);
}

通过使用冒泡排序,您可以保持 O(n) 复杂性,因为冒泡排序的一个特点是,如果只有一个条目不合适,它会在 O(n) 时间内执行。

我把它留给学生去实现 bubbleSort

可以使用 SortedSet 实现来完成这项工作:

class PQsort implements Comparator<Item> {

  public int compare(Item one, Item two) {
    return two.getValue() - one.getValue();
  }
}

...

Comparator<Item> itemComparator = new PQSort();
SortedSet<Item> top100 = new TreeSet<Item>(100, itemComparator);
Item smallestOfTheTop100 = null;

// Page through big data
List<Item> items = getNextPage(pageSize);
while (items.size() > 0) {

  for (Item current : items) {
    if (smallestOfTheLargest == null || itemComparator.compare(smallestOfTheTop100, current) > 0) {
         top100.add(item); // the current item is larger than the end of our top 100 list, so add it to the set.
         top100.remove(top100.first()); // remove the 101th element of the set - it is now extra. 
         smallestOfTheTop100 = top100.first(); 
    }
  }

  // Move to next page
  items = getNextPage(pageSize);
}

正如 sprinter 在他的回答中所说,它也可以在并行实现中重新设计 - 例如使用流。

我会坚持使用 PriorityQueue,只在它大于必要时删除项目。

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 10, 2, 9, 3, 7, 4, 6, 5, 7, 7, 7);

    findNHighest(list, 3);
    findNHighest(list, 1);
    findNHighest(list, 4);

}

private static void findNHighest(List<Integer> list, int n) {
    Queue<Integer> nthHighest = new PriorityQueue<>();

    for (Integer each : list) {
        nthHighest.add(each);
        if (nthHighest.size() > n) {
            nthHighest.poll();
        }
    }
    System.out.println(nthHighest);
}

输出

[7, 9, 10]
[10]
[7, 7, 9, 10]

构建优先级队列需要 MlogM,其中 M 是项目总数,然后弹出 N 个项目需要额外的 NlogM。 这比使用 MlogM 排序数组然后 select 在 N.

中的最后 N 项要贵一些

如果 N 很小,只需将数组迭代 N 次,每次取下一个最佳最大值。

标准解决方案是 Quick Select,具有 average 线性时间,here 是 Profesor 的实现罗伯特·塞奇威克。如果你需要 100 个最大的项目,select 最大的第 100 个元素,项目右边的所有元素都会更大。 教授就该主题进行了精彩的 video 讲座。

相关部分:

/***************************************************************************
*  Rearranges the elements in a so that a[k] is the kth smallest element,
*  and a[0] through a[k-1] are less than or equal to a[k], and
*  a[k+1] through a[n-1] are greater than or equal to a[k].
***************************************************************************/
public static <Key extends Comparable<Key>> Key select(Key[] a, int k) {
    if (k < 0 || k >= a.length) {
        throw new IndexOutOfBoundsException("Selected element out of bounds");
    }
    StdRandom.shuffle(a);
    int lo = 0, hi = a.length - 1;
    while (hi > lo) {
        int i = partition(a, lo, hi);
        if      (i > k) hi = i - 1;
        else if (i < k) lo = i + 1;
        else return a[i];
    }
    return a[lo];
}