5

我试图从整数数组输入中找到前 4 个最大值。例如,对于给定的输入数组 {1232, -1221, 0, 345, 78, 99} 将返回 {1232, 345, 99, 78} 作为前 4 个最大值。我已经用下面的方法解决了这个要求。但我仍然不满意它的时间效率。随着输入变大,是否有机会进一步优化该方法?任何线索都非常感谢。谢谢你。

public int[] findTopFourMax(int[] input) {
int[] topFourList = { Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,       Integer.MIN_VALUE };
for (int current : input) {
    if (current > topFourList[0]) {
        topFourList[3] = topFourList[2];
        topFourList[2] = topFourList[1];
        topFourList[1] = topFourList[0];
        topFourList[0] = current;
    } else if (current > topFourList[1]) {
        topFourList[3] = topFourList[2];
        topFourList[2] = topFourList[1];
        topFourList[1] = current;
    } else if (current > topFourList[2]) {
        topFourList[3] = topFourList[2];
        topFourList[2] = current;
    } else if (current > topFourList[3]) {
        topFourList[3] = current;
    }
}
return topFourList;

}

4

6 回答 6

13

最简单(虽然不是最有效)的方法是对包含最后 4 个元素的子数组进行排序。

您可以使用Arrays.sort()排序和Arrays.copyOfRange()获取子数组。

int[] arr = new int[] {1232, -1221, 0, 345, 78, 99};
Arrays.sort(arr);
int[] top4 = Arrays.copyOfRange(arr, arr.length-4,arr.length);
System.out.println(Arrays.toString(top4));

为了更有效的解决方案,可以维护前 K个元素的最小堆或使用选择算法找到前 4 个元素。此线程中描述了这两种方法。

尽管选择算法提供了O(n)解决方案,但最小堆解决方案(即O(nlogK))应该有更好的常数,尤其是对于小k的可能更快。

PS(编辑):

对于 4 个元素,您可能会发现调用循环 4 次,并在每个元素中找到一个最大值(并在每次迭代中将旧的最大值更改为 -infinity)将比更“复杂”的方法更有效,因为它需要顺序读取并且具有相当小的常量。对于 large ,这当然不是真的,而是k衰减到O(n^2)fork->n


EDIT2:基准测试:

为了好玩,我对附加的代码进行了基准测试。结果是:

[naive, sort, heap] = [9032, 214902, 7531]

我们可以看到,naive 和 heap 比基于排序的方法要好得多,并且 naive 比基于堆的方法稍慢。我做了一个wilcoxon 测试来检查 naive 和 heap 之间的差异是否具有统计显着性,我得到的 P_Value 为3.4573e-17. 这意味着两种方法“相同”的概率为 3.4573e-17(极小)。由此我们可以得出结论——基于堆的解决方案比单纯的排序解决方案具有更好的性能(我们已经通过经验证明了这一点!)。

附件:我使用的代码:

public static int[] findTopKNaive(int[] arr, int k) {
    int[] res = new int[k];
    for (int j = 0; j < k; j++) { 
        int max=Integer.MIN_VALUE, maxIdx = -1;
        for (int i = 0; i < arr.length; i++) { 
            if (max < arr[i]) { 
                max = arr[i];
                maxIdx = i;
            }
        }
        arr[maxIdx] = Integer.MIN_VALUE;
        res[k-1-j] = max;
    }
    return res;
}

public static int[] findTopKSort(int[] arr, int k) { 
    Arrays.sort(arr);
    return Arrays.copyOfRange(arr, arr.length-k,arr.length);
}

public static int[] findTopKHeap(int[] arr, int k) { 
    PriorityQueue<Integer> pq = new PriorityQueue<Integer>();
    for (int x : arr) { 
        if (pq.size() < k) pq.add(x);
        else if (pq.peek() < x) {
            pq.poll();
            pq.add(x);
        }
    }
    int[] res = new int[k];
    for (int i =0; i < k; i++) res[i] = pq.poll();
    return res;

}
public static int[] createRandomArray(int n, Random r) { 
    int[] arr = new int[n];
    for (int i = 0; i < n; i++) arr[i] = r.nextInt();
    return arr;
}
public static void main(String... args) throws Exception {
    Random r = new Random(1);
    int k = 4;
    int repeats = 200;
    int n = 5000000;
    long[][] results = new long[3][repeats];
    for (int i = 0; i < repeats; i++) { 
        int[] arr = createRandomArray(n, r);
        int[] myCopy;
        myCopy = Arrays.copyOf(arr, n);
        long start = System.currentTimeMillis();
        findTopKNaive(myCopy, k);
        results[0][i] = System.currentTimeMillis() - start;
        myCopy = Arrays.copyOf(arr, n);
        start = System.currentTimeMillis();
        findTopKSort(myCopy, k);
        results[1][i] = System.currentTimeMillis() - start;
        myCopy = Arrays.copyOf(arr, n);
        start = System.currentTimeMillis();
        findTopKHeap(myCopy, k);
        results[2][i] = System.currentTimeMillis() - start;
    }
    long[] sums = new long[3];
    for (int i = 0; i < repeats; i++) 
        for (int j = 0; j < 3; j++)
        sums[j] += results[j][i];
    System.out.println(Arrays.toString(sums));

    System.out.println("results for statistic test:");
    for (int i = 0; i < repeats; i++) { 
        System.out.println(results[0][i] + " " + results[2][i]);
    }
}
于 2013-01-02T13:01:16.137 回答
2

您应该查看Peter Lawrey 的这个答案。基本上,这个想法是遍历您的数组,将每个元素添加到 aSortedSet并通过在每次迭代中删除最少元素来保持大小为 4。这个过程是 O(n),即使在最坏的情况下,与 O(n logn) 典型和 O(n 2 ) 最坏情况相比,完全排序一个数组。

final List<Integer> input = new ArrayList(Arrays.asList(1232, -1221, 0, 345, 78, 99));
final NavigableSet<Integer> topFour = new TreeSet<>();
for (int i : input) {
  topFour.add(i);
  if (topFour.size() > 4) topFour.remove(topFour.first());
}
System.out.println(topFour);
于 2013-01-02T13:10:03.353 回答
1

最简单的方法是对数组进行排序并取前/后 4 个元素。

最后,最多 4 个条目可以在任何地方,所以无论你做什么,你都需要读取整个数组,这将是一个 O(n) 操作。

于 2013-01-02T13:01:26.727 回答
1

前面提到的对数组进行排序确实提供了最简单的方法,但并不是最有效的。

QuickSort (Quickselect) 的一种变体,可用于查找集合中的第 k 个最大值/最小值。

http://en.wikipedia.org/wiki/Selection_algorithm

正确的实现允许您在 O(n) 时间内获得第 k 个最大的值。

基本上,您使用枢轴进行分区,就像在快速排序中一样,并将每次迭代后的枢轴位置与您想要的位置(在您的情况下为四个)进行比较,如果相等,则返回该位置,否则,将算法应用于输入的正确一半.

当您找到第 k 个最大值的索引时,您可以简单地再次遍历数组并获得低于input[k].

对于您的情况,这可能有点过头了,因为您正好需要四个,但这是最通用的方法。

如果您不太关心内存,您还可以使用保留顶部/底部 X 值的 Bounded PriorityQueue,并将所有内容插入队列中。剩下的是您感兴趣的值。

于 2013-01-02T13:03:10.520 回答
1

排序:对数组进行排序并取最后四个元素

最小堆:最简单的解决方案是保持最大大小为 4 的最小堆

该解决方案的复杂度为 O(nlogk),其中 n 是元素数,k 是您需要的元素数。

Priority Queue:您可以创建一个PriorityQueue具有固定大小和自定义比较器的自定义比较器,如this question with implementation 中所述。

选择算法:您可以使用选择算法,您可以找到第 (nk) 个最大元素,然后返回所有高于该元素但较难实现的元素。最佳案例复杂度:O(n)

于 2013-01-02T13:15:17.400 回答
-1
float a[] = {1.0f,3.0f,5.0f,6.0f,7.0f,10.0f,11.0f,3.2f,4.0f};

float first =0.0f;
float second=0.0f;
float third =0.0f;
for (int i=0; i<a.length; i++){
    if(first < a[i]){
        first=a[i];
    }
}
System.out.println("first largest is "+first);
for (int j=0; j<a.length; j++){
    if(a[j] <first && a[j] > second){
        second = a[j];
    }
}
System.out.println("second largest is "+second);
for (int k=0;k<a.length; k++){
    if(a[k]<second && a[k]>third){
        third =a[k];
    }
}
System.out.println("third largest is "+third);
于 2014-08-06T22:10:08.493 回答